From d74f7eae17894b6a639e90ab15c22a411d2604b3 Mon Sep 17 00:00:00 2001 From: Nikesh kumar Date: Sat, 13 Apr 2024 12:19:25 +0530 Subject: [PATCH] fix(rest): Added a commit message while create a moderation request. Signed-off-by: Nikesh kumar --- pom.xml | 27 + .../component/ComponentController.java | 58 +- .../core/RestControllerHelper.java | 6 + .../project/ProjectController.java | 4511 +++++++++-------- 4 files changed, 2340 insertions(+), 2262 deletions(-) diff --git a/pom.xml b/pom.xml index 83b2e3f3aa..607d7b6c62 100644 --- a/pom.xml +++ b/pom.xml @@ -164,6 +164,33 @@ 2.3.1 26.0.5 6.3.3 + 4.0.1 + + + ${session.executionRootDirectory}/deploy + + + ${base.deploy.dir}/tomcat + + ${base.deploy.dir}/tomcat + + ${base.deploy.dir}/tomcat + + ${base.deploy.dir}/jars + + + -Dcatalina.home=${project.build.directory}/home -Xms128m -Xmx256m -XX:MaxMetaspaceSize=512m diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java index 772b631d34..3a7c760da6 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/ComponentController.java @@ -40,6 +40,7 @@ import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; import org.eclipse.sw360.datahandler.thrift.projects.Project; +import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityDTO; @@ -96,6 +97,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import static org.eclipse.sw360.datahandler.permissions.PermissionUtils.makePermission; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @BasePathAwareController @@ -109,6 +111,8 @@ public class ComponentController implements RepresentationModelProcessor RESPONSE_BODY_FOR_MODERATION_REQUEST = ImmutableMap.builder() .put("message", "Moderation request is created").build(); + 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 Sw360ComponentService componentService; @@ -346,25 +350,31 @@ public ResponseEntity> patchComponent( @Parameter(description = "The id of the component to be updated.") @PathVariable("id") String id, @Parameter(description = "The component with updated fields.") - @RequestBody ComponentDTO updateComponentDto + @RequestBody ComponentDTO updateComponentDto, + @RequestParam(value = "comment", required = false) String comment ) throws TException { User user = restControllerHelper.getSw360UserFromAuthentication(); Component sw360Component = componentService.getComponentForUserById(id, user); sw360Component = this.restControllerHelper.updateComponent(sw360Component, updateComponentDto); + user.setCommentMadeDuringModerationRequest(comment); Set attachmentDTOS = updateComponentDto.getAttachmentDTOs(); if (!CommonUtils.isNullOrEmptyCollection(attachmentDTOS)) { Set attachments = new HashSet<>(); - for (AttachmentDTO attachmentDTO: attachmentDTOS) { + for (AttachmentDTO attachmentDTO : attachmentDTOS) { attachments.add(restControllerHelper.convertToAttachment(attachmentDTO, user)); } sw360Component.setAttachments(attachments); } - RequestStatus updateComponentStatus = componentService.updateComponent(sw360Component, user); - HalResource userHalResource = createHalComponent(sw360Component, user); - if (updateComponentStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + if (!restControllerHelper.isWriteActionAllowed(sw360Component, user) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.FORBIDDEN); + } else { + RequestStatus updateComponentStatus = componentService.updateComponent(sw360Component, user); + HalResource userHalResource = createHalComponent(sw360Component, user); + if (updateComponentStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + return new ResponseEntity<>(userHalResource, HttpStatus.OK); } - return new ResponseEntity<>(userHalResource, HttpStatus.OK); } @PreAuthorize("hasAuthority('WRITE')") @@ -484,18 +494,26 @@ public ResponseEntity> patchComponentAttachmentInfo( @Parameter(description = "The id of the attachment.") @PathVariable("attachmentId") String attachmentId, @Parameter(description = "The attachment info to be updated.") - @RequestBody Attachment attachmentData + @RequestBody Attachment attachmentData, + @RequestParam(value = "comment", required = false) String comment + ) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Component sw360Component = componentService.getComponentForUserById(id, sw360User); Set attachments = sw360Component.getAttachments(); - Attachment updatedAttachment = attachmentService.updateAttachment(attachments, attachmentData, attachmentId, sw360User); + sw360User.setCommentMadeDuringModerationRequest(comment); + Attachment updatedAttachment = attachmentService.updateAttachment(attachments, attachmentData, attachmentId, + sw360User); RequestStatus updateComponentStatus = componentService.updateComponent(sw360Component, sw360User); - if (updateComponentStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + if (!restControllerHelper.isWriteActionAllowed(sw360Component, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.FORBIDDEN); + } else { + if (updateComponentStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + EntityModel attachmentResource = EntityModel.of(updatedAttachment); + return new ResponseEntity<>(attachmentResource, HttpStatus.OK); } - EntityModel attachmentResource = EntityModel.of(updatedAttachment); - return new ResponseEntity<>(attachmentResource, HttpStatus.OK); } @Operation( @@ -531,10 +549,15 @@ public ResponseEntity addAttachmentToComponent( @Parameter(description = "The file to be uploaded.") @RequestPart("file") MultipartFile file, @Parameter(description = "The attachment info to be created.") - @RequestPart("attachment") Attachment newAttachment + @RequestPart("attachment") Attachment newAttachment, + @RequestParam(value = "comment", required = false) String comment ) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Component component = componentService.getComponentForUserById(componentId, sw360User); + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(component, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } Attachment attachment = null; try { attachment = attachmentService.uploadAttachment(file, newAttachment, sw360User); @@ -609,10 +632,13 @@ public void downloadAttachmentBundleFromComponent( @DeleteMapping(COMPONENTS_URL + "/{componentId}/attachments/{attachmentIds}") public ResponseEntity> deleteAttachmentsFromComponent( @PathVariable("componentId") String componentId, - @PathVariable("attachmentIds") List attachmentIds) throws TException { + @PathVariable("attachmentIds") List attachmentIds, @RequestParam(value = "comment", required = false) String comment) throws TException { User user = restControllerHelper.getSw360UserFromAuthentication(); Component component = componentService.getComponentForUserById(componentId, user); - + user.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(component, user) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.FORBIDDEN); + } Set attachmentsToDelete = attachmentService.filterAttachmentsToRemove(Source.componentId(componentId), component.getAttachments(), attachmentIds); if (attachmentsToDelete.isEmpty()) { 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 14c51ca90c..ebee517c08 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 @@ -51,6 +51,7 @@ import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.projects.ProjectDTO; +import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; @@ -115,6 +116,7 @@ import java.util.stream.Collectors; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.permissions.PermissionUtils.makePermission; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @Service @@ -1655,4 +1657,8 @@ public ClearingRequest updateCRSize(ClearingRequest clearingRequest, Project pro } return clearingRequestService.getClearingRequestById(clearingRequest.getId(), sw360User); } + + public boolean isWriteActionAllowed(Object object, User user) { + return makePermission(object, user).isActionAllowed(RequestedAction.WRITE); + } } 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 58a96a36d2..17917a7201 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 @@ -167,6 +167,7 @@ import static org.eclipse.sw360.datahandler.common.CommonUtils.wrapThriftOptionalReplacement; import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; +import static org.eclipse.sw360.datahandler.permissions.PermissionUtils.makePermission; import static org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer.REPORT_FILENAME_MAPPING; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -184,23 +185,20 @@ public class ProjectController implements RepresentationModelProcessor mapOfFieldsTobeEmbedded = ImmutableMap.builder() - .put(Project._Fields.CLEARING_TEAM, "clearingTeam") - .put(Project._Fields.EXTERNAL_URLS, "externalUrls") - .put(Project._Fields.MODERATORS, "sw360:moderators") - .put(Project._Fields.CONTRIBUTORS,"sw360:contributors") - .put(Project._Fields.ATTACHMENTS,"sw360:attachments").build(); + .put(Project._Fields.CLEARING_TEAM, "clearingTeam").put(Project._Fields.EXTERNAL_URLS, "externalUrls") + .put(Project._Fields.MODERATORS, "sw360:moderators").put(Project._Fields.CONTRIBUTORS, "sw360:contributors") + .put(Project._Fields.ATTACHMENTS, "sw360:attachments").build(); private static final ImmutableMap mapOfProjectFieldsToRequestBody = ImmutableMap.builder() - .put(Project._Fields.VISBILITY, "visibility") - .put(Project._Fields.RELEASE_ID_TO_USAGE, "linkedReleases") + .put(Project._Fields.VISBILITY, "visibility").put(Project._Fields.RELEASE_ID_TO_USAGE, "linkedReleases") .put(Project._Fields.RELEASE_RELATION_NETWORK, "dependencyNetwork").build(); - private static final ImmutableMap RESPONSE_BODY_FOR_MODERATION_REQUEST = ImmutableMap.builder() - .put("message", "Moderation request is created").build(); + private static final ImmutableMap RESPONSE_BODY_FOR_MODERATION_REQUEST = ImmutableMap + .builder().put("message", "Moderation request is created").build(); + private static final ImmutableMap RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT = ImmutableMap + .builder().put("message", "Unauthorized user or empty commit message passed.").build(); private static final List enumReleaseRelationshipValues = Stream.of(ReleaseRelationship.values()) - .map(ReleaseRelationship::name) - .collect(Collectors.toList()); + .map(ReleaseRelationship::name).collect(Collectors.toList()); private static final List enumMainlineStateValues = Stream.of(MainlineState.values()) - .map(MainlineState::name) - .collect(Collectors.toList()); + .map(MainlineState::name).collect(Collectors.toList()); @NonNull private final Sw360ProjectService projectService; @@ -238,27 +236,18 @@ public class ProjectController implements RepresentationModelProcessor>> getProjectsForUser( - Pageable pageable, - @Parameter(description = "The name of the project") - @RequestParam(value = "name", required = false) String name, - @Parameter(description = "The type of the project") - @RequestParam(value = "type", required = false) String projectType, - @Parameter(description = "The group of the project") - @RequestParam(value = "group", required = false) String group, - @Parameter(description = "The tag of the project") - @RequestParam(value = "tag", required = false) String tag, - @Parameter(description = "Flag to get projects with all details.") - @RequestParam(value = "allDetails", required = false) boolean allDetails, - @Parameter(description = "List project by lucene search") - @RequestParam(value = "luceneSearch", required = false) boolean luceneSearch, - HttpServletRequest request) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + public ResponseEntity>> getProjectsForUser(Pageable pageable, + @Parameter(description = "The name of the project") @RequestParam(value = "name", required = false) String name, + @Parameter(description = "The type of the project") @RequestParam(value = "type", required = false) String projectType, + @Parameter(description = "The group of the project") @RequestParam(value = "group", required = false) String group, + @Parameter(description = "The tag of the project") @RequestParam(value = "tag", required = false) String tag, + @Parameter(description = "Flag to get projects with all details.") @RequestParam(value = "allDetails", required = false) boolean allDetails, + @Parameter(description = "List project by lucene search") @RequestParam(value = "luceneSearch", required = false) boolean luceneSearch, + HttpServletRequest request) + throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Map mapOfProjects = new HashMap<>(); boolean isSearchByName = name != null && !name.isEmpty(); @@ -312,18 +301,19 @@ public ResponseEntity>> getProjectsForUser( @NotNull private ResponseEntity>> getProjectResponse(Pageable pageable, - String projectType, String group, String tag, boolean allDetails, boolean luceneSearch, - HttpServletRequest request, User sw360User, Map mapOfProjects, boolean isSearchByName, - List sw360Projects, boolean isNoFilter) throws ResourceClassNotFoundException, PaginationParameterException, URISyntaxException, TException { + String projectType, String group, String tag, boolean allDetails, boolean luceneSearch, + HttpServletRequest request, User sw360User, Map mapOfProjects, boolean isSearchByName, + List sw360Projects, boolean isNoFilter) + throws ResourceClassNotFoundException, PaginationParameterException, URISyntaxException, TException { sw360Projects.stream().forEach(prj -> mapOfProjects.put(prj.getId(), prj)); PaginationResult paginationResult; if (isNoFilter) { int totalCount = projectService.getMyAccessibleProjectCounts(sw360User); - paginationResult = restControllerHelper.paginationResultFromPaginatedList(request, pageable, - sw360Projects, SW360Constants.TYPE_PROJECT, totalCount); + paginationResult = restControllerHelper.paginationResultFromPaginatedList(request, pageable, sw360Projects, + SW360Constants.TYPE_PROJECT, totalCount); } else { - paginationResult = restControllerHelper.createPaginationResult(request, pageable, - sw360Projects, SW360Constants.TYPE_PROJECT); + paginationResult = restControllerHelper.createPaginationResult(request, pageable, sw360Projects, + SW360Constants.TYPE_PROJECT); } List> projectResources = new ArrayList<>(); @@ -360,36 +350,22 @@ private ResponseEntity>> getProjectResponse return new ResponseEntity<>(resources, status); } - @Operation( - description = "List all projects associated to the user.", - tags = {"Projects"} - ) + @Operation(description = "List all projects associated to the user.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/myprojects", method = RequestMethod.GET) - public ResponseEntity>> getProjectsFilteredForUser( - Pageable pageable, - @Parameter(description = "Projects with current user as creator.") - @RequestParam(value = CREATED_BY, required = false, defaultValue = "true") boolean createdBy, - @Parameter(description = "Projects with current user as moderator.") - @RequestParam(value = "moderator", required = false, defaultValue = "true") boolean moderator, - @Parameter(description = "Projects with current user as contributor.") - @RequestParam(value = "contributor", required = false, defaultValue = "true") boolean contributor, - @Parameter(description = "Projects with current user as owner.") - @RequestParam(value = "projectOwner", required = false, defaultValue = "true") boolean projectOwner, - @Parameter(description = "Projects with current user as lead architect.") - @RequestParam(value = "leadArchitect", required = false, defaultValue = "true") boolean leadArchitect, - @Parameter(description = "Projects with current user as project responsible.") - @RequestParam(value = "projectResponsible", required = false, defaultValue = "true") boolean projectResponsible, - @Parameter(description = "Projects with current user as security responsible.") - @RequestParam(value = "securityResponsible", required = false, defaultValue = "true") boolean securityResponsible, - @Parameter(description = "Projects with state as open.") - @RequestParam(value = "stateOpen", required = false, defaultValue = "true") boolean stateOpen, - @Parameter(description = "Projects with state as closed.") - @RequestParam(value = "stateClosed", required = false, defaultValue = "true") boolean stateClosed, - @Parameter(description = "Projects with state as in progress.") - @RequestParam(value = "stateInProgress", required = false, defaultValue = "true") boolean stateInProgress, - @Parameter(description = "Flag to get projects with all details.") - @RequestParam(value = "allDetails", required = false) boolean allDetails, - HttpServletRequest request) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + public ResponseEntity>> getProjectsFilteredForUser(Pageable pageable, + @Parameter(description = "Projects with current user as creator.") @RequestParam(value = CREATED_BY, required = false, defaultValue = "true") boolean createdBy, + @Parameter(description = "Projects with current user as moderator.") @RequestParam(value = "moderator", required = false, defaultValue = "true") boolean moderator, + @Parameter(description = "Projects with current user as contributor.") @RequestParam(value = "contributor", required = false, defaultValue = "true") boolean contributor, + @Parameter(description = "Projects with current user as owner.") @RequestParam(value = "projectOwner", required = false, defaultValue = "true") boolean projectOwner, + @Parameter(description = "Projects with current user as lead architect.") @RequestParam(value = "leadArchitect", required = false, defaultValue = "true") boolean leadArchitect, + @Parameter(description = "Projects with current user as project responsible.") @RequestParam(value = "projectResponsible", required = false, defaultValue = "true") boolean projectResponsible, + @Parameter(description = "Projects with current user as security responsible.") @RequestParam(value = "securityResponsible", required = false, defaultValue = "true") boolean securityResponsible, + @Parameter(description = "Projects with state as open.") @RequestParam(value = "stateOpen", required = false, defaultValue = "true") boolean stateOpen, + @Parameter(description = "Projects with state as closed.") @RequestParam(value = "stateClosed", required = false, defaultValue = "true") boolean stateClosed, + @Parameter(description = "Projects with state as in progress.") @RequestParam(value = "stateInProgress", required = false, defaultValue = "true") boolean stateInProgress, + @Parameter(description = "Flag to get projects with all details.") @RequestParam(value = "allDetails", required = false) boolean allDetails, + HttpServletRequest request) + throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Map mapOfProjects = new HashMap<>(); @@ -400,124 +376,108 @@ public ResponseEntity>> getProjectsFiltered .put(Project._Fields.PROJECT_OWNER.toString(), projectOwner) .put(Project._Fields.LEAD_ARCHITECT.toString(), leadArchitect) .put(Project._Fields.PROJECT_RESPONSIBLE.toString(), projectResponsible) - .put(Project._Fields.SECURITY_RESPONSIBLES.toString(), securityResponsible) - .build(); + .put(Project._Fields.SECURITY_RESPONSIBLES.toString(), securityResponsible).build(); ImmutableMap clearingState = ImmutableMap.builder() .put(ProjectClearingState.OPEN.toString(), stateOpen) .put(ProjectClearingState.CLOSED.toString(), stateClosed) - .put(ProjectClearingState.IN_PROGRESS.toString(), stateInProgress) - .build(); + .put(ProjectClearingState.IN_PROGRESS.toString(), stateInProgress).build(); List sw360Projects = projectService.getMyProjects(sw360User, userRoles); sw360Projects = projectService.getWithFilledClearingStatus(sw360Projects, clearingState); - return getProjectResponse(pageable, null, null, null, allDetails, true, request, sw360User, - mapOfProjects, true, sw360Projects, false); + return getProjectResponse(pageable, null, null, null, allDetails, true, request, sw360User, mapOfProjects, true, + sw360Projects, false); } - @Operation( - description = "Get all releases of license clearing.", - tags = {"Projects"} - ) + @Operation(description = "Get all releases of license clearing.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}/licenseClearing", method = RequestMethod.GET) public ResponseEntity licenseClearing( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Get the transitive releases.") - @RequestParam(value = "transitive", required = true) boolean transitive, - HttpServletRequest request - ) throws TException { + @Parameter(description = "Project ID", example = "376576") @PathVariable("id") String id, + @Parameter(description = "Get the transitive releases.") @RequestParam(value = "transitive", required = true) boolean transitive, + HttpServletRequest request) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); - final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); - List releases = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - return sw360Release; - })).collect(Collectors.toList()); + final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); + List releases = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + return sw360Release; + })).collect(Collectors.toList()); - List> releaseList = releases.stream().map(sw360Release -> wrapTException(() -> { - final Release embeddedRelease = restControllerHelper.convertToEmbeddedLinkedRelease(sw360Release); - final HalResource releaseResource = restControllerHelper.addEmbeddedReleaseLinks(embeddedRelease); - return releaseResource; - })).collect(Collectors.toList()); + List> releaseList = releases.stream().map(sw360Release -> wrapTException(() -> { + final Release embeddedRelease = restControllerHelper.convertToEmbeddedLinkedRelease(sw360Release); + final HalResource releaseResource = restControllerHelper.addEmbeddedReleaseLinks(embeddedRelease); + return releaseResource; + })).collect(Collectors.toList()); - HalResource userHalResource = createHalLicenseClearing(sw360Project, releaseList); - return new ResponseEntity<>(userHalResource, HttpStatus.OK); - } + HalResource userHalResource = createHalLicenseClearing(sw360Project, releaseList); + return new ResponseEntity<>(userHalResource, HttpStatus.OK); + } - @Operation( - description = "Get a single project.", - tags = {"Projects"} - ) + @Operation(description = "Get a single project.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.GET) public ResponseEntity> getProject( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id) throws TException { + @Parameter(description = "Project ID", example = "376576") @PathVariable("id") String id) + throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Project sw360Project = projectService.getProjectForUserById(id, sw360User); HalResource userHalResource = createHalProject(sw360Project, sw360User); return new ResponseEntity<>(userHalResource, HttpStatus.OK); } - @Operation( - description = "Get linked projects of a single project.", - tags = {"Projects"} - ) + @Operation(description = "Get linked projects of a single project.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}/linkedProjects", method = RequestMethod.GET) - public ResponseEntity> getLinkedProject(Pageable pageable, - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id,@RequestParam(value = "transitive", required = false) String transitive, HttpServletRequest request) - throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + public ResponseEntity> getLinkedProject(Pageable pageable, + @Parameter(description = "Project ID", example = "376576") @PathVariable("id") String id, + @RequestParam(value = "transitive", required = false) String transitive, HttpServletRequest request) + throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Proj = projectService.getProjectForUserById(id, sw360User); - final Set projectIdsInBranch = new HashSet<>(); - boolean isTransitive = Boolean.parseBoolean(transitive); + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Proj = projectService.getProjectForUserById(id, sw360User); + final Set projectIdsInBranch = new HashSet<>(); + boolean isTransitive = Boolean.parseBoolean(transitive); - Map linkedProjects = sw360Proj.getLinkedProjects(); + Map linkedProjects = sw360Proj.getLinkedProjects(); List keys = linkedProjects != null ? new ArrayList<>(linkedProjects.keySet()) : new ArrayList<>(); - List projects = keys.stream().map(projId -> wrapTException(() -> { - final Project sw360Project = projectService.getProjectForUserById(projId, sw360User); - return sw360Project; - })).collect(Collectors.toList()); - - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, - projects, SW360Constants.TYPE_PROJECT); - - final List> projectResources = paginationResult.getResources().stream() - .map(sw360Project -> wrapTException(() -> { - final Project embeddedProject = restControllerHelper.convertToEmbeddedLinkedProject(sw360Project); - final HalResource projectResource = new HalResource<>(embeddedProject); - if (isTransitive) { - projectService.addEmbeddedLinkedProject(sw360Project, sw360User, projectResource, - projectIdsInBranch); - } - return projectResource; - })).collect(Collectors.toList()); - - CollectionModel resources; - if (projectResources.size() == 0) { - resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, projectResources); - } - - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); - } + List projects = keys.stream().map(projId -> wrapTException(() -> { + final Project sw360Project = projectService.getProjectForUserById(projId, sw360User); + return sw360Project; + })).collect(Collectors.toList()); - @Operation( - description = "Get releases of linked projects of a single project.", - tags = {"Projects"} - ) + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, + projects, SW360Constants.TYPE_PROJECT); + + final List> projectResources = paginationResult.getResources().stream() + .map(sw360Project -> wrapTException(() -> { + final Project embeddedProject = restControllerHelper.convertToEmbeddedLinkedProject(sw360Project); + final HalResource projectResource = new HalResource<>(embeddedProject); + if (isTransitive) { + projectService.addEmbeddedLinkedProject(sw360Project, sw360User, projectResource, + projectIdsInBranch); + } + return projectResource; + })).collect(Collectors.toList()); + + CollectionModel resources; + if (projectResources.size() == 0) { + resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, projectResources); + } + + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } + + @Operation(description = "Get releases of linked projects of a single project.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}/linkedProjects/releases", method = RequestMethod.GET) - public ResponseEntity>> getReleasesOfLinkedProject(@Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id, HttpServletRequest request, Pageable pageable) - throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + public ResponseEntity>> getReleasesOfLinkedProject( + @Parameter(description = "Project ID", example = "376576") @PathVariable("id") String id, + HttpServletRequest request, Pageable pageable) + throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); List> result = new ArrayList<>(); @@ -534,51 +494,37 @@ public ResponseEntity>> getReleasesOfLinked CollectionModel> emptyModel = CollectionModel.empty(); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(emptyModel); } - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, releases, SW360Constants.TYPE_RELEASE); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, + releases, SW360Constants.TYPE_RELEASE); final List> releaseResources = paginationResult.getResources().stream() - .map(sw360Release -> wrapTException(() -> { - final Release embeddedRelease = restControllerHelper.convertToEmbeddedLinkedProjectsReleases(sw360Release); - final HalResource releaseResource = new HalResource<>(embeddedRelease); - return releaseResource; - })).collect(Collectors.toList()); + .map(sw360Release -> wrapTException(() -> { + final Release embeddedRelease = restControllerHelper + .convertToEmbeddedLinkedProjectsReleases(sw360Release); + final HalResource releaseResource = new HalResource<>(embeddedRelease); + return releaseResource; + })).collect(Collectors.toList()); - CollectionModel resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources);; + CollectionModel resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources); + ; HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; return new ResponseEntity<>(resources, status); } - @Operation( - description = "Delete a single project.", - tags = {"Projects"}, - responses = { - @ApiResponse( - responseCode = "200", description = "Project deleted." - ), - @ApiResponse( - responseCode = "202", description = "Request sent for moderation.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "{\"message\": \"Moderation request is created\"}" - )) - } - ), - @ApiResponse( - responseCode = "409", - description = "The project is used as a linked project. Cannot delete it." - ), - @ApiResponse( - responseCode = "500", description = "Failed to delete project." - ) - } - ) + @Operation(description = "Delete a single project.", tags = { "Projects" }, responses = { + @ApiResponse(responseCode = "200", description = "Project deleted."), + @ApiResponse(responseCode = "202", description = "Request sent for moderation.", content = { + @Content(mediaType = "application/json", examples = @ExampleObject(value = "{\"message\": \"Moderation request is created\"}")) }), + @ApiResponse(responseCode = "409", description = "The project is used as a linked project. Cannot delete it."), + @ApiResponse(responseCode = "500", description = "Failed to delete project.") }) @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.DELETE) - public ResponseEntity deleteProject( - @Parameter(description = "Project ID") - @PathVariable("id") String id - ) throws TException { + public ResponseEntity deleteProject(@Parameter(description = "Project ID") @PathVariable("id") String id, @RequestParam(value = "comment", required = false) String comment) + throws TException { User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(projectService.getProjectForUserById(id, sw360User), sw360User) && comment == null) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } RequestStatus requestStatus = projectService.deleteProject(id, sw360User); if (requestStatus == RequestStatus.SUCCESS) { return new ResponseEntity<>(HttpStatus.OK); @@ -591,16 +537,13 @@ public ResponseEntity deleteProject( } } + @PreAuthorize("hasAuthority('WRITE')") - @Operation( - description = "Create a project.", - tags = {"Projects"} - ) + @Operation(description = "Create a project.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL, method = RequestMethod.POST) public ResponseEntity createProject( - @Parameter(schema = @Schema(implementation = Project.class)) - @RequestBody Map reqBodyMap - ) throws URISyntaxException, TException { + @Parameter(schema = @Schema(implementation = Project.class)) @RequestBody Map reqBodyMap) + throws URISyntaxException, TException { Project project = convertToProject(reqBodyMap); if (project.getReleaseIdToUsage() != null) { @@ -623,26 +566,20 @@ public ResponseEntity createProject( } project = projectService.createProject(project, sw360User); - URI location = ServletUriComponentsBuilder - .fromCurrentRequest().path("/{id}") - .buildAndExpand(project.getId()).toUri(); + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(project.getId()) + .toUri(); HalResource halResource = createHalProject(project, sw360User); return ResponseEntity.created(location).body(halResource); } @PreAuthorize("hasAuthority('WRITE')") - @Operation( - description = "Create a duplicate project.", - tags = {"Projects"} - ) + @Operation(description = "Create a duplicate project.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/duplicate/{id}", method = RequestMethod.POST) public ResponseEntity> createDuplicateProject( - @Parameter(description = "Project ID to copy.") - @PathVariable("id") String id, - @Parameter(schema = @Schema(implementation = Project.class)) - @RequestBody Map reqBodyMap - ) throws TException { + @Parameter(description = "Project ID to copy.") @PathVariable("id") String id, + @Parameter(schema = @Schema(implementation = Project.class)) @RequestBody Map reqBodyMap) + throws TException { if (!reqBodyMap.containsKey("name") && !reqBodyMap.containsKey("version")) { throw new HttpMessageNotReadableException( "Field name or version should be present in request body to create duplicate of a project"); @@ -672,11 +609,8 @@ public ResponseEntity> createDuplicateProject( } @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Link releases to the project.", - description = "Pass an array of release ids to be linked as request body.", - tags = {"Projects"} - ) + @Operation(summary = "Link releases to the project.", description = "Pass an array of release ids to be linked as request body.", tags = { + "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}/releases", method = RequestMethod.POST) public ResponseEntity linkReleases( @Parameter(description = "Project ID.") @@ -688,8 +622,15 @@ public ResponseEntity linkReleases( // TODO: Add example for MAP value } ) - @RequestBody Object releasesInRequestBody + @RequestBody Object releasesInRequestBody, + @RequestParam(value = "comment", required = false) String comment ) throws URISyntaxException, TException { + User user = restControllerHelper.getSw360UserFromAuthentication(); + Project updateProject = projectService.getProjectForUserById(id, user); + if (!restControllerHelper.isWriteActionAllowed(updateProject, user) && comment == null) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } + user.setCommentMadeDuringModerationRequest(comment); RequestStatus linkReleasesStatus = addOrPatchReleasesToProject(id, releasesInRequestBody, false); HttpStatus status = HttpStatus.CREATED; if (linkReleasesStatus == RequestStatus.SENT_TO_MODERATOR) { @@ -698,6 +639,7 @@ public ResponseEntity linkReleases( return new ResponseEntity<>(HttpStatus.CREATED); } + @PreAuthorize("hasAuthority('WRITE')") @Operation( summary = "Link project to an array of projects.", @@ -705,7 +647,7 @@ public ResponseEntity linkReleases( tags = {"Projects"} ) @RequestMapping(value = PROJECTS_URL + "/{id}/linkProjects", method = RequestMethod.POST) - public ResponseEntity linkToProjects( + public ResponseEntity linkToProjects( @Parameter(description = "Project ID.") @PathVariable("id") String id, @Parameter(description = "Array of project IDs", @@ -714,235 +656,128 @@ public ResponseEntity linkToProjects( // TODO: Add example for MAP value } ) - @RequestBody List projectIdsInRequestBody + @RequestBody List projectIdsInRequestBody, + @RequestParam(value = "comment", required = false) String comment ) throws URISyntaxException, TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sourceProj = projectService.getProjectForUserById(id, sw360User); - Map responseMap = new HashMap<>(); - HttpStatus status = null; - Set alreadyLinkedIds= new HashSet<>(); - Set idsSentToModerator = new HashSet<>(); - Set idsWithCyclicPath = new HashSet<>(); - Set linkedProjectIds = new HashSet<>(); - int count = 0; + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sourceProj = projectService.getProjectForUserById(id, sw360User); + Map responseMap = new HashMap<>(); + HttpStatus status = null; + Set alreadyLinkedIds = new HashSet<>(); + Set idsSentToModerator = new HashSet<>(); + Set idsWithCyclicPath = new HashSet<>(); + Set linkedProjectIds = new HashSet<>(); + int count = 0; - try { + try { + for (String projId : projectIdsInRequestBody) { + Project proj = projectService.getProjectForUserById(projId, sw360User); + Map linkedProject = Optional.ofNullable(proj.getLinkedProjects()).orElse(new HashMap<>()); - for(String projId: projectIdsInRequestBody) { - Project proj = projectService.getProjectForUserById(projId, sw360User); - Map linkedProject = Optional.ofNullable(proj.getLinkedProjects()).orElse(new HashMap<>()); + if (linkedProject.keySet().contains(id)) { + alreadyLinkedIds.add(projId); + continue; + } - if (linkedProject.keySet().contains(id)) { - alreadyLinkedIds.add(projId); - continue; - } + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(proj, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } else { + linkedProject.put(id, new ProjectProjectRelationship(ProjectRelationship.CONTAINED).setEnableSvm(sourceProj.isEnableSvm())); + proj.setLinkedProjects(linkedProject); + String cyclicLinkedProjectPath = projectService.getCyclicLinkedProjectPath(proj, sw360User); + if (!CommonUtils.isNullEmptyOrWhitespace(cyclicLinkedProjectPath)) { + idsWithCyclicPath.add(cyclicLinkedProjectPath); + continue; + } - linkedProject.put(id, new ProjectProjectRelationship(ProjectRelationship.CONTAINED).setEnableSvm(sourceProj.isEnableSvm())); - proj.setLinkedProjects(linkedProject); - String cyclicLinkedProjectPath = projectService.getCyclicLinkedProjectPath(proj, sw360User); - if (!CommonUtils.isNullEmptyOrWhitespace(cyclicLinkedProjectPath)) { - idsWithCyclicPath.add(cyclicLinkedProjectPath); - continue; - } + RequestStatus updatedStatus = projectService.updateProject(proj, sw360User); + if (updatedStatus == RequestStatus.SUCCESS) { + linkedProjectIds.add(projId); + } - RequestStatus updatedstatus = projectService.updateProject(proj, sw360User); - if (updatedstatus == RequestStatus.SUCCESS) { - linkedProjectIds.add(projId); + if (updatedStatus == RequestStatus.SENT_TO_MODERATOR) { + idsSentToModerator.add(projId); + } + } } - if (updatedstatus == RequestStatus.SENT_TO_MODERATOR) { - idsSentToModerator.add(projId); + if (!alreadyLinkedIds.isEmpty()) { + responseMap.put("Message regarding already linked project(s)", "Project ids are: " + alreadyLinkedIds); + status = HttpStatus.CONFLICT; + count++; + } + if (!idsWithCyclicPath.isEmpty()) { + responseMap.put("Message regarding project(s) having cyclic path", "Cyclic linked project path: " + idsWithCyclicPath); + status = HttpStatus.CONFLICT; + count++; + } + if (!idsSentToModerator.isEmpty()) { + responseMap.put("Message regarding project(s) sent to Moderator", "Project ids are: " + idsSentToModerator); + status = HttpStatus.ACCEPTED; + count++; + } + if (!linkedProjectIds.isEmpty()) { + responseMap.put("Message regarding successfully linked project(s)", "Project ids are: " + linkedProjectIds); + status = HttpStatus.CREATED; + count++; + } + if (count > 1) { + status = HttpStatus.CONFLICT; } - } - if (!alreadyLinkedIds.isEmpty()) { - responseMap.put("Message regarding already linked project(s)", "Project ids are: " + alreadyLinkedIds); - status = HttpStatus.CONFLICT; - count ++; - } - if (!idsWithCyclicPath.isEmpty()) { - responseMap.put("Message regarding project(s) having cyclic path", "Cyclic linked project path: " + idsWithCyclicPath); - status = HttpStatus.CONFLICT; - count ++; - } - if (!idsSentToModerator.isEmpty()) { - responseMap.put("Message regarding project(s) sent to Moderator", "Project ids are: " + idsSentToModerator); - status = HttpStatus.ACCEPTED; - count ++; - } - if (!linkedProjectIds.isEmpty()) { - responseMap.put("Message regarding successfully linked project(s)", "Project ids are: " + linkedProjectIds); - status = HttpStatus.CREATED; - count ++; - } - if (count > 1) { - status = HttpStatus.CONFLICT; + } catch (TException e) { + responseMap.put("ErrorMsg: ", "Error fetching project"); + status = HttpStatus.INTERNAL_SERVER_ERROR; + log.error("Error: ", e); } - } catch (TException e) { - responseMap.put("ErrorMsg: ", "Error fetching project"); - status = HttpStatus.INTERNAL_SERVER_ERROR; - log.error("Error: ", e); - } - - HalResource responseResource = new HalResource(responseMap); - return new ResponseEntity<>(responseResource, status); + HalResource responseResource = new HalResource(responseMap); + return new ResponseEntity<>(responseResource, status); } @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Append new releases to existing releases in a project.", - description = "Pass an array of release ids or a map of release id to usage to be linked as request body.", - tags = {"Projects"} - ) + @Operation(summary = "Append new releases to existing releases in a project.", description = "Pass an array of release ids or a map of release id to usage to be linked as request body.", tags = { + "Projects" }) @RequestMapping(value = PROJECTS_URL + "/{id}/releases", method = RequestMethod.PATCH) - public ResponseEntity patchReleases( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Array of release IDs to be linked.", - examples = { - @ExampleObject(value = "[\"3765276512\",\"5578999\",\"3765276513\"]"), - @ExampleObject(value = "[\"/releases/5578999\"]") - // TODO: Add example for MAP value - } - ) - @RequestBody Object releaseURIs - ) throws URISyntaxException, TException { - RequestStatus patchReleasesStatus = addOrPatchReleasesToProject(id, releaseURIs, true); - if (patchReleasesStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - return new ResponseEntity<>(HttpStatus.CREATED); - } - - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Add/link packages to the project.", - description = "Pass a set of package ids to be linked as request body.", - responses = { - @ApiResponse(responseCode = "201", description = "Packages are linked to the project."), - @ApiResponse(responseCode = "202", description = "Moderation request is created.") - }, - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/link/packages", method = RequestMethod.PATCH) - public ResponseEntity linkPackages( - @Parameter(description = "Project ID.", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Set of package IDs to be linked.", - example = "[\"3765276512\",\"5578999\",\"3765276513\"]" - ) - @RequestBody Set packagesInRequestBody - ) throws URISyntaxException, TException { - if(!packageService.validatePackageIds(packagesInRequestBody)){ - return new ResponseEntity<>("Package ID invalid! ", HttpStatus.NOT_FOUND); - } - RequestStatus linkPackageStatus = linkOrUnlinkPackages(id, packagesInRequestBody, true); - if (linkPackageStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - return new ResponseEntity<>(HttpStatus.CREATED); - } - - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Remove/unlink packages from the project.", - description = "Pass a set of package ids to be unlinked as request body.", - responses = { - @ApiResponse(responseCode = "201", description = "Packages are unlinked from the project."), - @ApiResponse(responseCode = "202", description = "Moderation request is created.") - }, - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/unlink/packages", method = RequestMethod.PATCH) - public ResponseEntity patchPackages( - @Parameter(description = "Project ID.", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Set of package IDs to be linked.", - example = "[\"3765276512\",\"5578999\",\"3765276513\"]" - ) - @RequestBody Set packagesInRequestBody - ) throws URISyntaxException, TException { - if(!packageService.validatePackageIds(packagesInRequestBody)){ - return new ResponseEntity<>("Package ID invalid! ", HttpStatus.NOT_FOUND); - } - RequestStatus patchPackageStatus = linkOrUnlinkPackages(id, packagesInRequestBody, false); - if (patchPackageStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - return new ResponseEntity<>(HttpStatus.CREATED); - } - - @Operation( - description = "Get releases of a single project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/releases", method = RequestMethod.GET) - public ResponseEntity>> getProjectReleases( - Pageable pageable, - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Get the transitive releases?") - @RequestParam(value = "transitive", required = false, defaultValue = "false") boolean transitive, - HttpServletRequest request - ) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { - + public ResponseEntity patchReleases(@Parameter(description = "Project ID.") @PathVariable("id") String id, + @Parameter(description = "Array of release IDs to be linked.", examples = { + @ExampleObject(value = "[\"3765276512\",\"5578999\",\"3765276513\"]"), + @ExampleObject(value = "[\"/releases/5578999\"]") + // TODO: Add example for MAP value + }) @RequestBody Object releaseURIs, + @RequestParam(value = "comment", required = false) String comment) + throws URISyntaxException, TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); - final Set releaseIdsInBranch = new HashSet<>(); - - List releases = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - return sw360Release; - })).collect(Collectors.toList()); - - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, - releases, SW360Constants.TYPE_RELEASE); - - final List> releaseResources = paginationResult.getResources().stream() - .map(sw360Release -> wrapTException(() -> { - final Release embeddedRelease = restControllerHelper.convertToEmbeddedRelease(sw360Release); - final HalResource releaseResource = new HalResource<>(embeddedRelease); - if (transitive) { - projectService.addEmbeddedlinkedRelease(sw360Release, sw360User, releaseResource, - releaseService, releaseIdsInBranch); - } - return releaseResource; - })).collect(Collectors.toList()); - - CollectionModel resources; - if (releaseResources.size() == 0) { - resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); + Project project = projectService.getProjectForUserById(id, sw360User); + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); } else { - resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources); + RequestStatus patchReleasesStatus = addOrPatchReleasesToProject(id, releaseURIs, true); + if (patchReleasesStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + return new ResponseEntity<>(HttpStatus.CREATED); } - - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); } - @Operation( - description = "Get releases of multiple projects.", - tags = {"Projects"} - ) + + @Operation(description = "Get releases of multiple projects.", tags = { "Projects" }) @RequestMapping(value = PROJECTS_URL + "/releases", method = RequestMethod.GET) - public ResponseEntity>> getProjectsReleases( - Pageable pageable, - @Parameter(description = "List of project IDs to get release for.", example = "[\"376576\",\"376570\"]") - @RequestBody List projectIds, - @Parameter(description = "Get the transitive releases") - @RequestParam(value = "transitive", required = false) boolean transitive, - @Parameter(description = "The clearing state of the release.", - schema = @Schema(implementation = ClearingState.class)) - @RequestParam(value = "clearingState", required = false) String clState, - HttpServletRequest request - ) throws URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + public ResponseEntity>> getProjectsReleases(Pageable pageable, + @Parameter(description = "List of project IDs to get release for.", example = "[\"376576\",\"376570\"]") @RequestBody List projectIds, + @Parameter(description = "Get the transitive releases") @RequestParam(value = "transitive", required = false) boolean transitive, + @Parameter(description = "The clearing state of the release.", schema = @Schema(implementation = ClearingState.class)) @RequestParam(value = "clearingState", required = false) String clState, + HttpServletRequest request) + throws URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Set releaseIdsInBranch = new HashSet<>(); - Set releases = projectService.getReleasesFromProjectIds(projectIds, transitive, sw360User, releaseService); + Set releases = projectService.getReleasesFromProjectIds(projectIds, transitive, sw360User, + releaseService); if (null != clState) { ClearingState cls = ThriftEnumUtils.stringToEnum(clState, ClearingState.class); @@ -951,7 +786,8 @@ public ResponseEntity>> getProjectsReleases List relList = releases.stream().collect(Collectors.toList()); - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, relList, SW360Constants.TYPE_RELEASE); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, + relList, SW360Constants.TYPE_RELEASE); final List> releaseResources = paginationResult.getResources().stream() .map(sw360Release -> wrapTException(() -> { final Release embeddedRelease = restControllerHelper.convertToEmbeddedReleaseWithDet(sw360Release); @@ -972,2070 +808,2258 @@ public ResponseEntity>> getProjectsReleases return new ResponseEntity<>(resources, status); } - @Operation( - description = "Get all releases with ECC information of a single project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/releases/ecc", method = RequestMethod.GET) - public ResponseEntity>> getECCsOfReleases( - Pageable pageable, - HttpServletRequest request, - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Get the transitive ECC") - @RequestParam(value = "transitive", required = false) boolean transitive - ) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + @Operation(description = "Patch vulnerabilities of a single project.", tags = {"Projects"}) + @RequestMapping(value = PROJECTS_URL + + "/{id}/vulnerabilities", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity>> updateVulnerabilitiesOfReleases( + @Parameter(description = "Project ID.") @PathVariable("id") String id, + @Parameter(description = "Vulnerability list") @RequestBody List vulnDTOs, @RequestParam(value = "comment", required = false) String comment) throws TException{ final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - List releases = new ArrayList<>(); - final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); - for (final String releaseId : releaseIds) { - Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); - releases.add(sw360Release); - } - - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, releases, SW360Constants.TYPE_RELEASE); - final List> releaseResources = new ArrayList<>(); - for (Release rel : paginationResult.getResources()) { - Release embeddedRelease = restControllerHelper.convertToEmbeddedRelease(rel); - embeddedRelease.setEccInformation(rel.getEccInformation()); - final EntityModel releaseResource = EntityModel.of(embeddedRelease); - releaseResources.add(releaseResource); - } - - CollectionModel> resources; - if (releaseIds.size() == 0) { - resources = restControllerHelper.emptyPageResource(Release.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources); - } - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); - } + // Fetch project object by ID + Project project = projectService.getProjectForUserById(id, sw360User); - @Operation( - description = "Get vulnerabilities of all projects including parent and directly linked projects.", - tags = {"Projects"} - ) + // Fetch vulnerabilities of the project + List actualVDto = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); + Set actualExternalId = actualVDto.stream().map(VulnerabilityDTO::getExternalId) + .collect(Collectors.toSet()); + Set externalIdsFromRequestDto = vulnDTOs.stream().map(VulnerabilityDTO::getExternalId) + .collect(Collectors.toSet()); + Set commonExtIds = Sets.intersection(actualExternalId, externalIdsFromRequestDto); - @RequestMapping(value = PROJECTS_URL + "/{id}/vulnerabilitySummary", method = RequestMethod.GET) - public ResponseEntity>> getAllVulnerabilities( - Pageable pageable, - HttpServletRequest request, - @PathVariable("id") String id) - throws TException, PaginationParameterException, ResourceClassNotFoundException, URISyntaxException { + if (CommonUtils.isNullOrEmptyCollection(commonExtIds) + || commonExtIds.size() != externalIdsFromRequestDto.size()) { + throw new HttpMessageNotReadableException("External ID is not valid"); + } - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); + Set actualReleaseIds = actualVDto.stream().map(VulnerabilityDTO::getIntReleaseId) + .collect(Collectors.toSet()); + Set releaseIdsFromRequestDto = vulnDTOs.stream().map(VulnerabilityDTO::getIntReleaseId) + .collect(Collectors.toSet()); + Set commonRelIds = Sets.intersection(actualReleaseIds, releaseIdsFromRequestDto); - List parentProjectVulnerabilities = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); + if (CommonUtils.isNullOrEmptyCollection(commonRelIds) + || commonRelIds.size() != releaseIdsFromRequestDto.size()) { + throw new HttpMessageNotReadableException("Release ID is not valid"); + } - Map> vulnerabilitiesMap = new HashMap<>(); - String parentProjectKey = sw360Project.getName() + " " + sw360Project.getVersion(); - vulnerabilitiesMap.put(parentProjectKey, parentProjectVulnerabilities); + Optional projectVulnerabilityRatings = wrapThriftOptionalReplacement( + vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); + ProjectVulnerabilityRating link = updateProjectVulnerabilityRatingFromRequest( + projectVulnerabilityRatings, vulnDTOs, id, sw360User); - if (!sw360Project.getLinkedProjects().isEmpty()) { - for (String linkedProjectId : sw360Project.getLinkedProjects().keySet()) { - Project linkedProject = projectService.getProjectForUserById(linkedProjectId, sw360User); - List linkedProjectVulnerabilities = vulnerabilityService.getVulnerabilitiesByProjectId(linkedProjectId, sw360User); - String linkedProjectKey = linkedProject.getName() + " " + linkedProject.getVersion(); - vulnerabilitiesMap.put(linkedProjectKey, linkedProjectVulnerabilities); - } + if (!restControllerHelper.isWriteActionAllowed(project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); } - List newList = new ArrayList<>(); - Optional projectVulnerabilityRating = wrapThriftOptionalReplacement(vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); - Map>> vulnerabilityIdToStatusHistory = projectVulnerabilityRating - .map(ProjectVulnerabilityRating::getVulnerabilityIdToReleaseIdToStatus).orElseGet(HashMap::new); - + sw360User.setCommentMadeDuringModerationRequest(comment); - for (Map.Entry> entry : vulnerabilitiesMap.entrySet()) { - String projectName = entry.getKey(); - List vulnerabilities = entry.getValue(); + final RequestStatus requestStatus = vulnerabilityService.updateProjectVulnerabilityRating(link, sw360User); - for (VulnerabilityDTO vulnerability : vulnerabilities) { - String comment = "", action = "new"; - Map> vulRatingProj = vulnerabilityService.fillVulnerabilityMetadata(vulnerability, projectVulnerabilityRating); - vulnerability.setProjectRelevance(vulRatingProj.get(vulnerability.externalId).get(vulnerability.intReleaseId).toString()); - Map> relIdToCheckStatus = vulnerabilityIdToStatusHistory.get(vulnerability.externalId); - if(null != relIdToCheckStatus && relIdToCheckStatus.containsKey(vulnerability.intReleaseId)) { - List checkStatus = relIdToCheckStatus.get(vulnerability.intReleaseId); - comment = checkStatus.get(checkStatus.size()-1).getComment(); - action = checkStatus.get(checkStatus.size()-1).getProjectAction(); - } - vulnerability.setComment(comment); - vulnerability.setAction(action); - VulnerabilitySummary summ = new VulnerabilitySummary(); - summ.setProjectName(projectName); - summ.setExternalId(vulnerability.getExternalId()); - summ.setDescription(vulnerability.getDescription()); - summ.setTitle(vulnerability.getTitle()); - summ.setPriority(vulnerability.getPriority()); - summ.setPriorityToolTip(vulnerability.getPriorityToolTip()); - summ.setAction(vulnerability.getAction()); - summ.setComment(vulnerability.getComment()); - summ.setMatchedBy(vulnerability.getMatchedBy()); - summ.setUsedNeedle(vulnerability.getUsedNeedle()); - summ.setProjectRelevance(vulnerability.getProjectRelevance()); - summ.setIntReleaseId(vulnerability.getIntReleaseId()); - summ.setIntReleaseName(vulnerability.getIntReleaseName()); - newList.add(summ); - } - } - - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, newList, SW360Constants.TYPE_VULNERABILITYSUMMARY); - - final List> vulResources = paginationResult.getResources().stream() - .map(sw360Vul -> wrapTException(() -> { - final VulnerabilitySummary embeddedVul = restControllerHelper.convertToEmbeddedVulnerabilitySumm(sw360Vul); - final HalResource vulResource = new HalResource<>(embeddedVul); - Link projectLink = linkTo(VulnerabilityController.class) - .slash("api/vulnerabilities/" + sw360Vul.getExternalId()).withSelfRel(); - vulResource.add(projectLink); - return vulResource; - })).collect(Collectors.toList()); + final List> vulnerabilityResources = new ArrayList<>(); + vulnDTOs.forEach(dto -> { + final EntityModel vulnerabilityDTOEntityModel = EntityModel.of(dto); + vulnerabilityResources.add(vulnerabilityDTOEntityModel); + }); - CollectionModel resources; - if (vulResources.size() == 0) { - resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, vulResources); + CollectionModel> resources = null; + if (RequestStatus.SUCCESS.equals(requestStatus)) { + resources = restControllerHelper.createResources(vulnerabilityResources); } HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + + if (requestStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + return new ResponseEntity<>(resources, status); } - @Operation( - description = "Get vulnerabilities of a single project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/vulnerabilities", method = RequestMethod.GET) - public ResponseEntity>> getVulnerabilitiesOfReleases( - Pageable pageable, - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "The priority of vulnerability.", - examples = {@ExampleObject(value = "1 - critical"), @ExampleObject(value = "2 - major")} - ) - @RequestParam(value = "priority") Optional priority, - @Parameter(description = "The relevance of project of the vulnerability.", - schema = @Schema(implementation = VulnerabilityRatingForProject.class) - ) - @RequestParam(value = "projectRelevance") Optional projectRelevance, - @Parameter(description = "The release Id of vulnerability.") - @RequestParam(value = "releaseId") Optional releaseId, - @Parameter(description = "The external Id of vulnerability.") - @RequestParam(value = "externalId") Optional externalId, - HttpServletRequest request - ) throws URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final List allVulnerabilityDTOs = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); - Optional projectVulnerabilityRating = wrapThriftOptionalReplacement(vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); - Map>> vulnerabilityIdToStatusHistory = projectVulnerabilityRating - .map(ProjectVulnerabilityRating::getVulnerabilityIdToReleaseIdToStatus).orElseGet(HashMap::new); - final List> vulnerabilityResources = new ArrayList<>(); - for (final VulnerabilityDTO vulnerabilityDTO : allVulnerabilityDTOs) { - String comment = "", action = ""; - Map> vulRatingProj = vulnerabilityService.fillVulnerabilityMetadata(vulnerabilityDTO, projectVulnerabilityRating); - vulnerabilityDTO.setProjectRelevance(vulRatingProj.get(vulnerabilityDTO.externalId).get(vulnerabilityDTO.intReleaseId).toString()); - Map> relIdToCheckStatus = vulnerabilityIdToStatusHistory.get(vulnerabilityDTO.externalId); - if(null != relIdToCheckStatus && relIdToCheckStatus.containsKey(vulnerabilityDTO.intReleaseId)) { - List checkStatus = relIdToCheckStatus.get(vulnerabilityDTO.intReleaseId); - comment = checkStatus.get(checkStatus.size()-1).getComment(); - action = checkStatus.get(checkStatus.size()-1).getProjectAction(); - } - vulnerabilityDTO.setComment(comment); - vulnerabilityDTO.setAction(action); - final EntityModel vulnerabilityDTOEntityModel = EntityModel.of(vulnerabilityDTO); - vulnerabilityResources.add(vulnerabilityDTOEntityModel); - } - List priorityList = priority.isPresent() ? Lists.newArrayList(priority.get().split(",")) : Lists.newArrayList(); - List projectRelevanceList = projectRelevance.isPresent() ? Lists.newArrayList(projectRelevance.get().split(",")) : Lists.newArrayList(); - final List> vulnResources = vulnerabilityResources.stream() - .filter(vulRes -> projectRelevance.isEmpty() || projectRelevanceList.contains(vulRes.getContent().getProjectRelevance())) - .filter(vulRes -> priority.isEmpty() || priorityList.contains(vulRes.getContent().getPriority())) - .filter(vulRes -> !releaseId.isPresent() || vulRes.getContent().getIntReleaseId().equals(releaseId.get())) - .filter(vulRes -> !externalId.isPresent() || vulRes.getContent().getExternalId().equals(externalId.get())) - .collect(Collectors.toList()); - List vulDtos = vulnResources.stream().map(res -> res.getContent()).collect(Collectors.toList()); - PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, vulDtos, SW360Constants.TYPE_VULNERABILITYDTO); - List> paginatedVulnResources = Lists.newArrayList(); - for (VulnerabilityDTO vd: paginationResult.getResources()) { - EntityModel vDTOEntityModel = EntityModel.of(vd); - paginatedVulnResources.add(vDTOEntityModel); - } - CollectionModel resources; - if (vulnResources.size() == 0) { - resources = restControllerHelper.emptyPageResource(VulnerabilityDTO.class, paginationResult); - } else { - resources = restControllerHelper.generatePagesResource(paginationResult, paginatedVulnResources); - } + private Set getExcludedLicenses(Set excludedLicenseIds, + List licenseInfoParsingResult) { - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); + Predicate filteredLicense = licenseNameWithText -> excludedLicenseIds + .contains(licenseNameWithText.getLicenseName()); + Function> streamLicenseNameWithTexts = licenseInfo -> licenseInfo + .getLicenseNamesWithTexts().stream(); + return licenseInfoParsingResult.stream().map(LicenseInfoParsingResult::getLicenseInfo) + .flatMap(streamLicenseNameWithTexts).filter(filteredLicense).collect(Collectors.toSet()); } - @Operation( - description = "Patch vulnerabilities of a single project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/vulnerabilities", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity>> updateVulnerabilitiesOfReleases( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Vulnerability list") - @RequestBody List vulnDTOs - ) { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - - List actualVDto = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); - Set actualExternalId = actualVDto.stream().map(VulnerabilityDTO::getExternalId).collect(Collectors.toSet()); - Set externalIdsFromRequestDto = vulnDTOs.stream().map(VulnerabilityDTO::getExternalId).collect(Collectors.toSet()); - Set commonExtIds = Sets.intersection(actualExternalId, externalIdsFromRequestDto); - - if (CommonUtils.isNullOrEmptyCollection(commonExtIds) || commonExtIds.size() != externalIdsFromRequestDto.size()) { - throw new HttpMessageNotReadableException("External ID is not valid"); + private List> getReleaseObjectMapper(List> releaseList) { + ObjectMapper oMapper = new ObjectMapper(); + List> modifiedList = new ArrayList<>(); + for (EntityModel rel : releaseList) { + Map relMap = oMapper.convertValue(rel, Map.class); + if (relMap.get(ATTACHMENTS) != null) { + for (Object attach : (ArrayList) relMap.get(ATTACHMENTS)) { + Map attachmentMap = (Map) attach; + AttachmentType type = AttachmentType.valueOf((String) attachmentMap.get(ATTACHMENT_TYPE)); + attachmentMap.replace(ATTACHMENT_TYPE, attachmentMap.get(ATTACHMENT_TYPE), + ThriftEnumUtils.MAP_ATTACHMENT_TYPE_SHORT_STRING.get(type)); + } + } + final ImmutableSet fieldsToKeep = ImmutableSet.of("name", "version", ATTACHMENTS); + Map valueToKeep = new LinkedHashMap<>(); + Link releaseLink = null; + if (relMap != null) { + for (Map.Entry entry : relMap.entrySet()) { + if (entry != null && entry.getKey().equals("id")) { + releaseLink = linkTo(ReleaseController.class).slash("api/releases/" + entry.getValue()) + .withSelfRel(); + } else if (entry != null && fieldsToKeep.contains(entry.getKey())) { + if (entry.getKey().equals(ATTACHMENTS) && null != entry.getValue()) { + List> attList = new ArrayList<>(); + for (LinkedHashMap att : ((List) entry.getValue())) { + Map map = new LinkedHashMap<>(); + map.put("attachmentContentId", att.get("attachmentContentId")); + map.put("filename", att.get("filename")); + map.put("sha1", att.get("sha1")); + map.put(ATTACHMENT_TYPE, att.get(ATTACHMENT_TYPE)); + map.put(CREATED_BY, att.get(CREATED_BY)); + map.put("createdTeam", att.get("createdTeam")); + map.put("createdOn", att.get("createdOn")); + map.put("checkStatus", att.get("checkStatus")); + map.put("createdComment", att.get("createdComment")); + map.put("checkedBy", att.get("checkedBy")); + map.put("checkedTeam", att.get("checkedTeam")); + map.put("checkedComment", att.get("checkedComment")); + map.put("checkedOn", att.get("checkedOn")); + attList.add(map); + } + valueToKeep.put(entry.getKey(), attList); + } else { + valueToKeep.put(entry.getKey(), entry.getValue()); + } + } + } + } + Map linksValeToKeep = new LinkedHashMap<>(); + linksValeToKeep.put("self", releaseLink); + valueToKeep.put("_links", linksValeToKeep); + modifiedList.add(valueToKeep); } + return modifiedList; + } - Set actualReleaseIds = actualVDto.stream().map(VulnerabilityDTO::getIntReleaseId).collect(Collectors.toSet()); - Set releaseIdsFromRequestDto = vulnDTOs.stream().map(VulnerabilityDTO::getIntReleaseId).collect(Collectors.toSet()); - Set commonRelIds = Sets.intersection(actualReleaseIds, releaseIdsFromRequestDto); + public List filterReleases(User sw360User, String filter, Set releaseIds) { + List releasesSrc = new ArrayList<>(); - if(CommonUtils.isNullOrEmptyCollection(commonRelIds) || commonRelIds.size() != releaseIdsFromRequestDto.size()) { - throw new HttpMessageNotReadableException("Release ID is not valid"); + switch (filter) { + case "withSourceAttachment": + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + List sourceAttachments = sw360Release.getAttachments().stream() + .filter(attachment -> attachment.getAttachmentType() == AttachmentType.SOURCE + || attachment.getAttachmentType() == AttachmentType.SOURCE_SELF) + .collect(Collectors.toList()); + Set sourceAttachmentsSet = new HashSet<>(sourceAttachments); + sw360Release.setAttachments(sourceAttachmentsSet); + return sourceAttachmentsSet.isEmpty() ? null : sw360Release; + })).filter(Objects::nonNull).collect(Collectors.toList()); + break; + case "withoutSourceAttachment": + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + List withoutSourceAttachments = sw360Release.getAttachments().stream() + .filter(attachment -> attachment.getAttachmentType() != AttachmentType.SOURCE + && attachment.getAttachmentType() != AttachmentType.SOURCE_SELF) + .collect(Collectors.toList()); + Set withoutSourceAttachmentsSet = new HashSet<>(withoutSourceAttachments); + sw360Release.setAttachments(withoutSourceAttachmentsSet); + return withoutSourceAttachmentsSet.isEmpty() ? null : sw360Release; + })).filter(Objects::nonNull).collect(Collectors.toList()); + break; + case "withoutAttachment": + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + return sw360Release.getAttachments().isEmpty() ? sw360Release : null; + })).filter(Objects::nonNull).collect(Collectors.toList()); + break; + case "withAttachment": + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + return sw360Release.getAttachments().isEmpty() ? null : sw360Release; + })).filter(Objects::nonNull).collect(Collectors.toList()); + break; + case "withCliAttachment": + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + List cliAttachments = sw360Release.getAttachments().stream().filter( + attachment -> attachment.getAttachmentType() == AttachmentType.COMPONENT_LICENSE_INFO_XML) + .collect(Collectors.toList()); + Set cliAttachmentsSet = new HashSet<>(cliAttachments); + sw360Release.setAttachments(cliAttachmentsSet); + return cliAttachmentsSet.isEmpty() ? null : sw360Release; + })).filter(Objects::nonNull).collect(Collectors.toList()); + break; + default: + releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + return sw360Release; + })).collect(Collectors.toList()); + break; } - Optional projectVulnerabilityRatings = wrapThriftOptionalReplacement(vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); - ProjectVulnerabilityRating link = updateProjectVulnerabilityRatingFromRequest(projectVulnerabilityRatings, vulnDTOs, id, sw360User); - final RequestStatus requestStatus = vulnerabilityService.updateProjectVulnerabilityRating(link, sw360User); - final List> vulnerabilityResources = new ArrayList<>(); - vulnDTOs.forEach(dto->{ - final EntityModel vulnerabilityDTOEntityModel = EntityModel.of(dto); - vulnerabilityResources.add(vulnerabilityDTOEntityModel); - }); + return releasesSrc; + } - CollectionModel> resources = null; - if (RequestStatus.SUCCESS.equals(requestStatus)) { - resources = restControllerHelper.createResources(vulnerabilityResources); - } - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - if (requestStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + private HalResource attachmentUsageReleases(Project sw360Project, List> releases, + List> attachmentUsageMap) throws TException { + ObjectMapper oMapper = new ObjectMapper(); + Map releaseIdToUsages = sw360Project.getReleaseIdToUsage(); + Map projectMap = oMapper.convertValue(sw360Project, Map.class); + Map resultMap = new HashMap<>(); + resultMap.put("linkedProjects", projectMap.get("linkedProjects")); + resultMap.put("releaseIdToUsage", projectMap.get("releaseIdToUsage")); + + Map releaseIdToUsage = (Map) resultMap.get("releaseIdToUsage"); + final ImmutableSet fieldsToRemove = ImmutableSet.of("setCreatedBy", "setCreatedOn", "setComment", + "setReleaseRelation", "setMainlineState"); + if (releaseIdToUsage != null) { + for (Map.Entry entry : releaseIdToUsage.entrySet()) { + Map originalValue = (Map) entry.getValue(); + if (originalValue != null) { + for (String field : fieldsToRemove) { + originalValue.remove(field); + } + } + } } - return new ResponseEntity<>(resources, status); - } + HalResource halProject = new HalResource(resultMap); - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Link releases to the project with usage.", - description = "Pass a map of release id to usage to be linked as request body.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/release/{releaseId}", method = RequestMethod.PATCH) - public ResponseEntity> patchProjectReleaseUsage( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Release ID.") - @PathVariable("releaseId") String releaseId, - @Parameter(description = "Map of release id to usage.") - @RequestBody ProjectReleaseRelationship requestBodyProjectReleaseRelationship - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); - ProjectReleaseRelationship updatedProjectReleaseRelationship = projectService - .updateProjectReleaseRelationship(releaseIdToUsage, requestBodyProjectReleaseRelationship, releaseId); - RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, sw360User); - if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + if (releaseIdToUsages != null) { + restControllerHelper.addEmbeddedProjectAttachmentUsage(halProject, releases, attachmentUsageMap); } - EntityModel updatedProjectReleaseRelationshipResource = EntityModel.of( - updatedProjectReleaseRelationship); - return new ResponseEntity<>(updatedProjectReleaseRelationshipResource, HttpStatus.OK); + return halProject; } - public ProjectVulnerabilityRating updateProjectVulnerabilityRatingFromRequest(Optional projectVulnerabilityRatings, List vulDtoList, String projectId, User sw360User) { - Function fillVulnerabilityCheckStatus = vulDto -> { - return new VulnerabilityCheckStatus().setCheckedBy(sw360User.getEmail()) - .setCheckedOn(SW360Utils.getCreatedOn()) - .setVulnerabilityRating(ThriftEnumUtils.stringToEnum(vulDto.getProjectRelevance(), VulnerabilityRatingForProject.class)) - .setComment(vulDto.getComment() == null ? "" : vulDto.getComment()).setProjectAction(vulDto.getAction()); - }; - - ProjectVulnerabilityRating projectVulnerabilityRating = projectVulnerabilityRatings.orElse( - new ProjectVulnerabilityRating() - .setProjectId(projectId) - .setVulnerabilityIdToReleaseIdToStatus(new HashMap<>())); + private HalResource createHalProject(Project sw360Project, User sw360User) throws TException { + HalResource halProject = new HalResource<>(sw360Project); + User projectCreator = restControllerHelper.getUserByEmail(sw360Project.getCreatedBy()); + restControllerHelper.addEmbeddedUser(halProject, projectCreator, CREATED_BY); - if (!projectVulnerabilityRating.isSetVulnerabilityIdToReleaseIdToStatus()) { - projectVulnerabilityRating.setVulnerabilityIdToReleaseIdToStatus(new HashMap<>()); + Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); + if (releaseIdToUsage != null) { + restControllerHelper.addEmbeddedReleases(halProject, releaseIdToUsage.keySet(), releaseService, sw360User); } - Map>> vulnerabilityIdToReleaseIdToStatus = projectVulnerabilityRating.getVulnerabilityIdToReleaseIdToStatus(); - String[] vulnerabilityIds = vulDtoList.stream().map(dto -> dto.getExternalId()).toArray(String[]::new); - String[] releaseIds = vulDtoList.stream().map(dto -> dto.getIntReleaseId()).toArray(String[]::new); - VulnerabilityCheckStatus[] vulStatusCheck = vulDtoList.stream().map(dt -> fillVulnerabilityCheckStatus.apply(dt)).toArray(VulnerabilityCheckStatus[]::new); + Map linkedProjects = sw360Project.getLinkedProjects(); + if (linkedProjects != null) { + restControllerHelper.addEmbeddedProject(halProject, linkedProjects.keySet(), projectService, sw360User); + } - for (int i = 0; i < vulnerabilityIds.length; i++) { - String vulnerabilityId = vulnerabilityIds[i]; - String releaseId = releaseIds[i]; + if (sw360Project.getModerators() != null) { + Set moderators = sw360Project.getModerators(); + restControllerHelper.addEmbeddedModerators(halProject, moderators); + } - Map> releaseIdToStatus = vulnerabilityIdToReleaseIdToStatus.computeIfAbsent(vulnerabilityId, k -> new HashMap<>()); - List vulnerabilityCheckStatusHistory = releaseIdToStatus.computeIfAbsent(releaseId, k -> new ArrayList<>()); - VulnerabilityCheckStatus statustoBeUpdated = vulStatusCheck[i]; - if (!vulnerabilityCheckStatusHistory.isEmpty()) { - VulnerabilityCheckStatus latestStatusFromHistory = vulnerabilityCheckStatusHistory - .get(vulnerabilityCheckStatusHistory.size() - 1); - if (StringUtils.isEmpty(statustoBeUpdated.getComment())) { - statustoBeUpdated.setComment(latestStatusFromHistory.getComment()); - } - if (StringUtils.isEmpty(statustoBeUpdated.getProjectAction())) { - statustoBeUpdated.setProjectAction(latestStatusFromHistory.getProjectAction()); - } - if (Objects.isNull(statustoBeUpdated.getVulnerabilityRating())) { - statustoBeUpdated.setVulnerabilityRating(latestStatusFromHistory.getVulnerabilityRating()); - } - } - vulnerabilityCheckStatusHistory.add(statustoBeUpdated); + if (sw360Project.getAttachments() != null) { + restControllerHelper.addEmbeddedAttachments(halProject, sw360Project.getAttachments()); } - return projectVulnerabilityRating; - } + if (sw360Project.getLeadArchitect() != null) { + restControllerHelper.addEmbeddedLeadArchitect(halProject, sw360Project.getLeadArchitect()); + } - @Operation( - description = "Get license of releases of a single project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenses", method = RequestMethod.GET) - public ResponseEntity>> getLicensesOfReleases( - @Parameter(description = "Project ID.") - @PathVariable("id") String id - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project project = projectService.getProjectForUserById(id, sw360User); - final List> licenseResources = new ArrayList<>(); - final Set allLicenseIds = new HashSet<>(); + if (sw360Project.getContributors() != null) { + Set contributors = sw360Project.getContributors(); + restControllerHelper.addEmbeddedContributors(halProject, contributors); + } - final Set releaseIdToUsage = project.getReleaseIdToUsage().keySet(); - for (final String releaseId : releaseIdToUsage) { - final Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); - final Set licenseIds = sw360Release.getMainLicenseIds(); - if (licenseIds != null && !licenseIds.isEmpty()) { - allLicenseIds.addAll(licenseIds); - } + if (sw360Project.getVendor() != null) { + Vendor vendor = sw360Project.getVendor(); + Vendor vendorHalResource = restControllerHelper.convertToEmbeddedVendor(vendor); + halProject.addEmbeddedResource("sw360:vendors", vendorHalResource); + sw360Project.setVendor(null); } - for (final String licenseId : allLicenseIds) { - final License sw360License = licenseService.getLicenseById(licenseId); - final License embeddedLicense = restControllerHelper.convertToEmbeddedLicense(sw360License); - final EntityModel licenseResource = EntityModel.of(embeddedLicense); - licenseResources.add(licenseResource); + + if (sw360Project.getPackageIdsSize() > 0) { + restControllerHelper.addEmbeddedPackages(halProject, sw360Project.getPackageIds(), packageService); } - final CollectionModel> resources = restControllerHelper.createResources(licenseResources); - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); + return halProject; } - @Operation( - summary = "Download license info for the project.", - description = "Set the request parameter `&template=` for variant `REPORT` to choose " + - "specific template.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenseinfo", method = RequestMethod.GET) - public void downloadLicenseInfo( - @Parameter(description = "Project ID.", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Output generator class", - schema = @Schema(type = "string", - allowableValues = {"DocxGenerator", "XhtmlGenerator", - "TextGenerator"} - )) - @RequestParam("generatorClassName") String generatorClassName, - @Parameter(description = "Variant of the report", - schema = @Schema(implementation = OutputFormatVariant.class)) - @RequestParam("variant") String variant, - @Parameter(description = "The external Ids of the project", example = "376577") - @RequestParam(value = "externalIds", required = false) String externalIds, - @RequestParam(value = "template", required = false) String template, - @Parameter(description = "Generate license info including all attachments of the linked releases") - @RequestParam(value = "includeAllAttachments", required = false ) boolean includeAllAttachments, - @Parameter(description = "Exclude release version from the license info file") - @RequestParam(value = "excludeReleaseVersion", required = false, defaultValue = "false") boolean excludeReleaseVersion, - HttpServletResponse response - ) throws TException, IOException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - List mappedProjectLinks = new ArrayList<>(); - if (includeAllAttachments) { - mappedProjectLinks = projectService.createLinkedProjects(sw360Project, - projectService.filterAndSortAllAttachments(SW360Constants.INITIAL_LICENSE_INFO_ATTACHMENT_TYPES), true, sw360User); + private RequestStatus linkOrUnlinkPackages(String id, Set packagesInRequestBody, boolean link) + throws URISyntaxException, TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = projectService.getProjectForUserById(id, sw360User); + Set packageIds = new HashSet<>(); + if (!CommonUtils.isNullOrEmptyCollection(project.getPackageIds())) { + packageIds = project.getPackageIds(); + } + if (link) { + packageIds.addAll(packagesInRequestBody); } else { - mappedProjectLinks = projectService.createLinkedProjects(sw360Project, - projectService.filterAndSortAttachments(SW360Constants.LICENSE_INFO_ATTACHMENT_TYPES), true, sw360User); + packageIds.removeAll(packagesInRequestBody); } - List attchmntUsg = attachmentService.getAttachemntUsages(id); - - Map> releaseIdToExcludedLicenses = attchmntUsg.stream() - .collect(Collectors.toMap(AttachmentUsage::getOwner, - x -> x.getUsageData().getLicenseInfo().getExcludedLicenseIds(), (li1, li2) -> li1)); - - Map usedAttachmentContentIds = attchmntUsg.stream() - .collect(Collectors.toMap(AttachmentUsage::getAttachmentContentId, attUsage -> { - if (attUsage.isSetUsageData() - && attUsage.getUsageData().getSetField().equals(UsageData._Fields.LICENSE_INFO)) { - return Boolean.valueOf(attUsage.getUsageData().getLicenseInfo().isIncludeConcludedLicense()); - } - return Boolean.FALSE; - }, (li1, li2) -> li1)); - - final Map> selectedReleaseAndAttachmentIds = new HashMap<>(); - final Map> excludedLicensesPerAttachments = new HashMap<>(); + project.setPackageIds(packageIds); + return projectService.updateProject(project, sw360User); + } - mappedProjectLinks.forEach(projectLink -> wrapTException(() -> - projectLink.getLinkedReleases().stream().filter(ReleaseLink::isSetAttachments).forEach(releaseLink -> { - String releaseLinkId = releaseLink.getId(); - Set excludedLicenseIds = releaseIdToExcludedLicenses.get(Source.releaseId(releaseLinkId)); + private Project convertToProject(Map requestBody) { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.registerModule(sw360Module); - if (!selectedReleaseAndAttachmentIds.containsKey(releaseLinkId)) { - selectedReleaseAndAttachmentIds.put(releaseLinkId, new HashMap<>()); + if (requestBody.containsKey(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY))) { + try { + String visibility = (String) requestBody + .get(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY)); + requestBody.put(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY), + visibility.toUpperCase()); + } catch (IllegalArgumentException e) { + System.err.println("Error processing visibility field: " + e.getMessage()); + System.err.println("Failed requestBody: " + requestBody); + throw e; } - final List attachments = releaseLink.getAttachments(); - Release release = componentService.getReleaseById(releaseLinkId, sw360User); - for (final Attachment attachment : attachments) { - String attachemntContentId = attachment.getAttachmentContentId(); - if (includeAllAttachments) { - selectedReleaseAndAttachmentIds.get(releaseLinkId).put(attachemntContentId, - false); - } else { - if (usedAttachmentContentIds.containsKey(attachemntContentId)) { - boolean includeConcludedLicense = usedAttachmentContentIds.get(attachemntContentId); - List licenseInfoParsingResult = licenseInfoService - .getLicenseInfoForAttachment(release, sw360User, attachemntContentId, includeConcludedLicense); - excludedLicensesPerAttachments.put(attachemntContentId, getExcludedLicenses(excludedLicenseIds, licenseInfoParsingResult)); - selectedReleaseAndAttachmentIds.get(releaseLinkId).put(attachemntContentId, includeConcludedLicense); - } + } + + if (requestBody.containsKey("linkedProjects")) { + Map linkedProjects = (Map) requestBody.get("linkedProjects"); + linkedProjects.entrySet().stream().forEach(entry -> { + if (entry.getValue() instanceof String) { + Map projectRelationshipMap = new HashMap<>(); + projectRelationshipMap.put("projectRelationship", entry.getValue()); + linkedProjects.put(entry.getKey(), projectRelationshipMap); } - } - }))); - - final String projectName = sw360Project.getName(); - final String projectVersion = sw360Project.getVersion(); - final String timestamp = SW360Utils.getCreatedOnTime().replaceAll("\\s", "_").replace(":", "_"); - String outputGeneratorClassNameWithVariant = generatorClassName+"::"+variant; - final OutputFormatInfo outputFormatInfo = licenseInfoService.getOutputFormatInfoForGeneratorClass(generatorClassName); - final String filename = String.format("%s-%s%s-%s.%s", Strings.nullToEmpty(variant).equals("DISCLOSURE") ? "LicenseInfo" : "ProjectClearingReport", projectName, - StringUtils.isBlank(projectVersion) ? "" : "-" + projectVersion, timestamp, - outputFormatInfo.getFileExtension()); - - String fileName = ""; - if (CommonUtils.isNotNullEmptyOrWhitespace(template) && CommonUtils.isNotNullEmptyOrWhitespace(REPORT_FILENAME_MAPPING)) { - Map orgToTemplate = Arrays.stream(REPORT_FILENAME_MAPPING.split(",")) - .collect(Collectors.toMap(k -> k.split(":")[0], v -> v.split(":")[1])); - fileName = orgToTemplate.get(template); - } - - final LicenseInfoFile licenseInfoFile = licenseInfoService.getLicenseInfoFile(sw360Project, sw360User, - outputGeneratorClassNameWithVariant, selectedReleaseAndAttachmentIds, excludedLicensesPerAttachments, - externalIds, fileName, excludeReleaseVersion); - byte[] byteContent = licenseInfoFile.bufferForGeneratedOutput().array(); - response.setContentType(outputFormatInfo.getMimeType()); - response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)); - FileCopyUtils.copy(byteContent, response.getOutputStream()); + }); + } + return mapper.convertValue(requestBody, Project.class); } - private Set getExcludedLicenses(Set excludedLicenseIds, - List licenseInfoParsingResult) { - - Predicate filteredLicense = licenseNameWithText -> excludedLicenseIds - .contains(licenseNameWithText.getLicenseName()); - Function> streamLicenseNameWithTexts = licenseInfo -> licenseInfo - .getLicenseNamesWithTexts().stream(); - return licenseInfoParsingResult.stream().map(LicenseInfoParsingResult::getLicenseInfo) - .flatMap(streamLicenseNameWithTexts).filter(filteredLicense).collect(Collectors.toSet()); + public static TSerializer getJsonSerializer() { + try { + return new TSerializer(new TSimpleJSONProtocol.Factory()); + } catch (TTransportException e) { + log.error("Error creating TSerializer " + e); + } + return null; } - @Operation( - description = "Get all attachment information of a project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/attachments", method = RequestMethod.GET) - public ResponseEntity>> getProjectAttachments( - @Parameter(description = "Project ID.") - @PathVariable("id") String id - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - final CollectionModel> resources = attachmentService.getResourcesFromList(sw360Project.getAttachments()); - return new ResponseEntity<>(resources, HttpStatus.OK); - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - description = "Update and attachment usage for project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/attachment/{attachmentId}", method = RequestMethod.PATCH) - public ResponseEntity> patchProjectAttachmentInfo( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Attachment ID.") - @PathVariable("attachmentId") String attachmentId, - @RequestBody Attachment attachmentData - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - Set attachments = sw360Project.getAttachments(); - Attachment updatedAttachment = attachmentService.updateAttachment(attachments, attachmentData, attachmentId, sw360User); - RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, sw360User); - if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - EntityModel attachmentResource = EntityModel.of(updatedAttachment); - return new ResponseEntity<>(attachmentResource, HttpStatus.OK); - } - @Operation( - summary = "Download an attachment of a project", - description = "Download an attachment of a project. Set the Accept-Header `application/*`. " + - "Only this Accept-Header is supported.", - responses = @ApiResponse( - content = {@Content(mediaType = "application/*")} - ), - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments/{attachmentId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public void downloadAttachmentFromProject( - @Parameter(description = "Project ID.") - @PathVariable("projectId") String projectId, - @Parameter(description = "Attachment ID.") - @PathVariable("attachmentId") String attachmentId, - HttpServletResponse response - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project project = projectService.getProjectForUserById(projectId, sw360User); - this.attachmentService.downloadAttachmentWithContext(project, attachmentId, response, sw360User); - } @Operation( - description = "Download clearing reports as a zip.", - responses = @ApiResponse( - content = {@Content(mediaType = "application/zip")} - ), - tags = {"Projects"} + description = "Get obligation data of project tab.", + tags = {"Project"} ) - @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments/clearingReports", method = RequestMethod.GET, produces = "application/zip") - public void downloadClearingReports( - @Parameter(description = "Project ID.") - @PathVariable("projectId") String projectId, - HttpServletResponse response - ) throws TException { + @RequestMapping(value = PROJECTS_URL + "/{id}/obligation", method = RequestMethod.GET) + public ResponseEntity getObligations(Pageable pageable, + @Parameter(description = "Project ID.") @PathVariable("id") String id, + @Parameter(description = "Obligation Level", + schema = @Schema(allowableValues = {"license", "project", "organization", "component"})) + @RequestParam(value = "obligationLevel", required = true) String oblLevel) + throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project project = projectService.getProjectForUserById(projectId, sw360User); - final String filename = "Clearing-Reports-" + project.getName() + ".zip"; + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + final Map releaseIdToAcceptedCLI = Maps.newHashMap(); + List releases = new ArrayList<>();; + ObligationList obligation = new ObligationList(); + Map obligationStatusMap = Maps.newHashMap(); + Map oblData = Maps.newHashMap(); + Map filterData = Maps.newHashMap(); - final Set attachments = project.getAttachments(); - final Set clearingAttachments = new HashSet<>(); - for (final Attachment attachment : attachments) { - if (attachment.getAttachmentType().equals(AttachmentType.CLEARING_REPORT)) { - clearingAttachments.add(attachmentService.getAttachmentContent(attachment.getAttachmentContentId())); + List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); + for (final String releaseId : releaseIds) { + Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); + if (sw360Release.getAttachmentsSize() > 0) { + releases.add(sw360Release); } } - - try (InputStream attachmentStream = attachmentService.getStreamToAttachments(clearingAttachments, sw360User, project)) { - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)); - FileCopyUtils.copy(attachmentStream, response.getOutputStream()); - } catch (final TException | IOException e) { - log.error(e.getMessage()); - } - } - - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - description = "Update a project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.PATCH) - public ResponseEntity> patchProject( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Updated values", schema = @Schema(implementation = Project.class)) - @RequestBody Map reqBodyMap - ) throws TException { - User user = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, user); - Project updateProject = convertToProject(reqBodyMap); - updateProject.unsetReleaseRelationNetwork(); - sw360Project = this.restControllerHelper.updateProject(sw360Project, updateProject, reqBodyMap, mapOfProjectFieldsToRequestBody); - if (SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP && updateProject.getReleaseIdToUsage() != null) { - sw360Project.unsetReleaseRelationNetwork(); - projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(sw360Project, user); - } - RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, user); - HalResource userHalResource = createHalProject(sw360Project, user); - if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - return new ResponseEntity<>(userHalResource, HttpStatus.OK); - } - - @Operation( - description = "Add attachments to a project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments", method = RequestMethod.POST, consumes = {"multipart/mixed", "multipart/form-data"}) - public ResponseEntity addAttachmentToProject( - @Parameter(description = "Project ID.") - @PathVariable("projectId") String projectId, - @Parameter(description = "File to attach") - @RequestPart("file") MultipartFile file, - @Parameter(description = "Attachment description") - @RequestPart("attachment") Attachment newAttachment - ) throws TException { - 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); + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { + obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); + obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); + oblData = projectService.setObligationsFromAdminSection(sw360User, obligationStatusMap, sw360Project, oblLevel); + } else { + oblData = projectService.setObligationsFromAdminSection(sw360User, new HashMap(), sw360Project, oblLevel); } - project.addToAttachments(attachment); - RequestStatus updateProjectStatus = projectService.updateProject(project, sw360User); - HttpStatus status = HttpStatus.OK; - HalResource halResource = createHalProject(project, sw360User); - if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + if (oblLevel.equalsIgnoreCase("License")) { + for (Map.Entry entry : obligationStatusMap.entrySet()) { + String key = entry.getKey(); + if (!key.equals("compObl") && !key.equals("projectObl") && !key.equals("orgObl")) { + filterData.put(key, entry.getValue()); + } + } + releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(filterData)); + oblData = projectService.setLicenseInfoWithObligations(filterData, releaseIdToAcceptedCLI, releases, sw360User); + for (Map.Entry entry : oblData.entrySet()) { + ObligationStatusInfo statusInfo = entry.getValue(); + Set limitedSet = releaseService.getReleasesForUserByIds(statusInfo.getReleaseIdToAcceptedCLI().keySet()); + statusInfo.setReleases(limitedSet); + } } - return new ResponseEntity<>(halResource, status); - } - @Operation( - summary = "Get all projects corresponding to external ids.", - description = "The request parameter supports MultiValueMap (allows to add duplicate keys with different " + - "values). It's possible to search for projects only by the external id key by leaving the value.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/searchByExternalIds", method = RequestMethod.GET) - public ResponseEntity searchByExternalIds( - @Parameter(description = "External ID map for filter.", - example = "{\"project-ext\": \"515432\", \"project-ext\": \"7657\", \"portal-id\": \"13319-XX3\"}" - ) - HttpServletRequest request - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - String queryString = request.getQueryString(); - return restControllerHelper.searchByExternalIds(queryString, projectService, sw360User); + Map responseBody = createPaginationMetadata(pageable, oblData); + HalResource> halObligation = new HalResource<>(responseBody); + return new ResponseEntity<>(halObligation, HttpStatus.OK); } - @Operation( - description = "Get all the projects where the project is used.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/usedBy/{id}", method = RequestMethod.GET) - public ResponseEntity>> getUsedByProjectDetails( - @Parameter(description = "Project ID to search.") - @PathVariable("id") String id - ) throws TException { - User user = restControllerHelper.getSw360UserFromAuthentication(); - Set sw360Projects = projectService.searchLinkingProjects(id, user); - - List> projectResources = new ArrayList<>(); - sw360Projects.forEach(p -> { - Project embeddedProject = restControllerHelper.convertToEmbeddedProject(p); - projectResources.add(EntityModel.of(embeddedProject)); - }); - - CollectionModel> resources = restControllerHelper.createResources(projectResources); - HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; - return new ResponseEntity<>(resources, status); - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Save attachment usages", - description = "Pass an array of string in request body.", - tags = {"Projects"} - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "AttachmentUsages Saved Successfully.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "{\"message\": \"AttachmentUsages Saved Successfully\"}" - )) - }), - @ApiResponse( - responseCode = "403", description = "No write permission for project.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "{\"message\": \"No write permission for project\"}" - )) - }), - @ApiResponse( - responseCode = "409", description = "Not a valid attachment type OR release does not belong to project.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "{\"message\": \"Not a valid attachment type OR release does not belong to project\"}" - )) - } - ), - @ApiResponse( - responseCode = "500", description = "Internal Server Error.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "{\"message\": \"Saving attachment usages for project 123456 failed\"}" - )) - } - ) - }) - @RequestMapping(value = PROJECTS_URL + "/{id}/saveAttachmentUsages", method = RequestMethod.POST) - public ResponseEntity saveAttachmentUsages( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Map of key-value pairs where each key is associated with a list of strings.", - schema = @Schema( - example = """ - { - "selected": [ - "4427a8e723ad405db63f75170ef240a2_sourcePackage_5c5d6f54ac6a4b33bcd3c5d3a8fefc43", "value2" - ], - "deselected": [ - "de213309ba0842ac8a7251bf27ea8f36_manuallySet_eec66c3465f64f0292dfc2564215c681", "value2" - ], - "selectedConcludedUsages": [ - "de213309ba0842ac8a7251bf27ea8f36_licenseInfo_eec66c3465f64f0292dfc2564215c681", "value2" - ], - "deselectedConcludedUsages": [ - "ade213309ba0842ac8a7251bf27ea8f36_licenseInfo_aeec66c3465f64f0292dfc2564215c681", "value2" - ] - } - """ - ) - ) - @RequestBody Map> allUsages - ) throws TException { - final User user = restControllerHelper.getSw360UserFromAuthentication(); - final Project project = projectService.getProjectForUserById(id, user); - try { - if (PermissionUtils.makePermission(project, user).isActionAllowed(RequestedAction.WRITE)) { - Source usedBy = Source.projectId(id); - List selectedUsages = new ArrayList<>(); - List deselectedUsages = new ArrayList<>(); - List selectedConcludedUsages = new ArrayList<>(); - List deselectedConcludedUsages = new ArrayList<>(); - List changedUsages = new ArrayList<>(); - for (Map.Entry> entry : allUsages.entrySet()) { - String key = entry.getKey(); - List list = entry.getValue(); - switch (key) { - case "selected" -> selectedUsages.addAll(list); - case "deselected" -> deselectedUsages.addAll(list); - case "selectedConcludedUsages" -> selectedConcludedUsages.addAll(list); - case "deselectedConcludedUsages" -> deselectedConcludedUsages.addAll(list); - } - } - Set totalReleaseIds = projectService.getReleaseIds(id, user, true); - changedUsages.addAll(selectedUsages); - changedUsages.addAll(deselectedUsages); - boolean valid = projectService.validate(changedUsages, user, releaseService, totalReleaseIds); - if (!valid) { - return new ResponseEntity<>("Not a valid attachment type OR release does not belong to project", HttpStatus.CONFLICT); - } - List allUsagesByProject = projectService.getUsedAttachments(usedBy, null); - List savedUsages = projectService.savedUsages(allUsagesByProject); - savedUsages.removeAll(deselectedUsages); - deselectedUsages.addAll(selectedUsages); - selectedUsages.addAll(savedUsages); - deselectedConcludedUsages.addAll(selectedConcludedUsages); - List deselectedUsagesFromRequest = projectService.deselectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id); - List selectedUsagesFromRequest = projectService.selectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id); - List usagesToDelete = allUsagesByProject.stream() - .filter(usage -> deselectedUsagesFromRequest.stream() - .anyMatch(projectService.isUsageEquivalent(usage))) - .collect(Collectors.toList()); - if (!usagesToDelete.isEmpty()) { - projectService.deleteAttachmentUsages(usagesToDelete); + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Add/link packages to the project.", + description = "Pass a set of package ids to be linked as request body.", + responses = { + @ApiResponse(responseCode = "201", description = "Packages are linked to the project."), + @ApiResponse(responseCode = "202", description = "Moderation request is created.") + }, + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/link/packages", method = RequestMethod.PATCH) + public ResponseEntity linkPackages( + @Parameter(description = "Project ID.", example = "376576") + @PathVariable("id") String id, + @Parameter(description = "Set of package IDs to be linked.", + example = "[\"3765276512\",\"5578999\",\"3765276513\"]" + ) + @RequestBody Set packagesInRequestBody, + @RequestParam(value = "comment", required = false) String comment + ) throws URISyntaxException, TException { + if (!packageService.validatePackageIds(packagesInRequestBody)) { + return new ResponseEntity<>("Package ID invalid! ", HttpStatus.NOT_FOUND); + } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = projectService.getProjectForUserById(id, sw360User); + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } else { + RequestStatus linkPackageStatus = linkOrUnlinkPackages(id, packagesInRequestBody, true); + if (linkPackageStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); } - List allUsagesByProjectAfterCleanUp = projectService.getUsedAttachments(usedBy, null); - List usagesToCreate = selectedUsagesFromRequest.stream() - .filter(usage -> allUsagesByProjectAfterCleanUp.stream() - .noneMatch(projectService.isUsageEquivalent(usage))) - .collect(Collectors.toList()); + return new ResponseEntity<>(HttpStatus.CREATED); + } + } - if (!usagesToCreate.isEmpty()) { - projectService.makeAttachmentUsages(usagesToCreate); - } - return new ResponseEntity<>("AttachmentUsages Saved Successfully", HttpStatus.CREATED); + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Remove/unlink packages from the project.", + description = "Pass a set of package ids to be unlinked as request body.", + responses = { + @ApiResponse(responseCode = "201", description = "Packages are unlinked from the project."), + @ApiResponse(responseCode = "202", description = "Moderation request is created.") + }, + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/unlink/packages", method = RequestMethod.PATCH) + public ResponseEntity patchPackages( + @Parameter(description = "Project ID.", example = "376576") + @PathVariable("id") String id, + @Parameter(description = "Set of package IDs to be linked.", + example = "[\"3765276512\",\"5578999\",\"3765276513\"]" + ) + @RequestBody Set packagesInRequestBody, + @RequestParam(value = "comment", required = false) String comment + ) throws URISyntaxException, TException { + if (!packageService.validatePackageIds(packagesInRequestBody)) { + return new ResponseEntity<>("Package ID invalid! ", HttpStatus.NOT_FOUND); + } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = projectService.getProjectForUserById(id, sw360User); + sw360User.setCommentMadeDuringModerationRequest(comment); + if (!restControllerHelper.isWriteActionAllowed(project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); } else { - return new ResponseEntity<>("No write permission for project", HttpStatus.FORBIDDEN); + RequestStatus patchPackageStatus = linkOrUnlinkPackages(id, packagesInRequestBody, false); + if (patchPackageStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + return new ResponseEntity<>(HttpStatus.CREATED); } - } catch (TException e) { - return new ResponseEntity<>("Saving attachment usages for project " + id + " failed", HttpStatus.INTERNAL_SERVER_ERROR); } - } - - @Operation( - description = "Get all attachmentUsages of the projects.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/attachmentUsage", method = RequestMethod.GET) - public ResponseEntity attachmentUsages( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter( - description = "filtering attachmentUsages", - schema = @Schema(allowableValues = { - "withSourceAttachment", "withoutSourceAttachment", "withoutAttachment", - "withAttachment", "withCliAttachment"}) - ) - @RequestParam(value = "filter", required = false) String filter, - @Parameter(description = "Get the transitive releases.") - @RequestParam(value = "transitive", required = true) boolean transitive - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); - final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); - List releases = null; - if (filter != null) { - releases = filterReleases(sw360User, filter, releaseIds); - } else { - releases = releaseIds.stream().map(relId -> wrapTException(() -> { + @Operation( + description = "Get releases of a single project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/releases", method = RequestMethod.GET) + public ResponseEntity>> getProjectReleases( + Pageable pageable, + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Get the transitive releases?") + @RequestParam(value = "transitive", required = false, defaultValue = "false") boolean transitive, + HttpServletRequest request + ) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); + final Set releaseIdsInBranch = new HashSet<>(); + + List releases = releaseIds.stream().map(relId -> wrapTException(() -> { final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); return sw360Release; })).collect(Collectors.toList()); - } - List> releaseList = releases.stream().map(sw360Release -> wrapTException(() -> { - final Release embeddedRelease = restControllerHelper.convertToEmbeddedReleaseAttachments(sw360Release); - final HalResource releaseResource = restControllerHelper.addEmbeddedReleaseLinks(embeddedRelease); - return releaseResource; - })).collect(Collectors.toList()); - - List attachmentUsages = attachmentService.getAllAttachmentUsage(id); - String prefix = "{\"" + SW360_ATTACHMENT_USAGES + "\":["; - String serializedUsages = attachmentUsages.stream() - .map(usage -> wrapTException(() -> THRIFT_JSON_SERIALIZER.toString(usage))) - .collect(Collectors.joining(",", prefix, "]}")); - GsonJsonParser parser = new GsonJsonParser(); - Map attachmentUsageMap = parser.parseMap(serializedUsages); - List> listOfAttachmentUsages = (List>) attachmentUsageMap - .get(SW360_ATTACHMENT_USAGES); - listOfAttachmentUsages.removeIf(Objects::isNull); - for (Map attachmentUsage : listOfAttachmentUsages) { - attachmentUsage.remove("revision"); - attachmentUsage.remove("type"); - Object usageData=attachmentUsage.get("usageData"); - if (usageData != null) { - Map licenseInfo = ((Map>) usageData).get("licenseInfo"); - if (licenseInfo != null) { - Object includeConcludedLicense = licenseInfo.get("includeConcludedLicense"); - if (includeConcludedLicense != null) { - if ((Double)includeConcludedLicense == 0.0) { - licenseInfo.put("includeConcludedLicense", false); - } else { - licenseInfo.put("includeConcludedLicense", true); - } - } - } - } - } - List> releaseObjMap = getReleaseObjectMapper(releaseList); - HalResource userHalResource = attachmentUsageReleases(sw360Project, releaseObjMap, listOfAttachmentUsages); - return new ResponseEntity<>(userHalResource, HttpStatus.OK); - } + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, + releases, SW360Constants.TYPE_RELEASE); - private List> getReleaseObjectMapper(List> releaseList) { - ObjectMapper oMapper = new ObjectMapper(); - List> modifiedList = new ArrayList<>(); - for (EntityModel rel : releaseList) { - Map relMap = oMapper.convertValue(rel, Map.class); - if (relMap.get(ATTACHMENTS) != null) { - for (Object attach : (ArrayList) relMap.get(ATTACHMENTS)) { - Map attachmentMap = (Map) attach; - AttachmentType type = AttachmentType.valueOf((String) attachmentMap.get(ATTACHMENT_TYPE)); - attachmentMap.replace(ATTACHMENT_TYPE, attachmentMap.get(ATTACHMENT_TYPE), - ThriftEnumUtils.MAP_ATTACHMENT_TYPE_SHORT_STRING.get(type)); - } - } - final ImmutableSet fieldsToKeep = ImmutableSet.of("name", "version", ATTACHMENTS); - Map valueToKeep = new LinkedHashMap<>(); - Link releaseLink = null; - if (relMap != null) { - for (Map.Entry entry : relMap.entrySet()) { - if (entry != null && entry.getKey().equals("id")) { - releaseLink = linkTo(ReleaseController.class).slash("api/releases/" + entry.getValue()) - .withSelfRel(); - } else if (entry != null && fieldsToKeep.contains(entry.getKey())) { - if (entry.getKey().equals(ATTACHMENTS) && null != entry.getValue()) { - List> attList = new ArrayList<>(); - for (LinkedHashMap att : ((List) entry.getValue())) { - Map map = new LinkedHashMap<>(); - map.put("attachmentContentId", att.get("attachmentContentId")); - map.put("filename", att.get("filename")); - map.put("sha1", att.get("sha1")); - map.put(ATTACHMENT_TYPE, att.get(ATTACHMENT_TYPE)); - map.put(CREATED_BY, att.get(CREATED_BY)); - map.put("createdTeam", att.get("createdTeam")); - map.put("createdOn", att.get("createdOn")); - map.put("checkStatus", att.get("checkStatus")); - map.put("createdComment", att.get("createdComment")); - map.put("checkedBy", att.get("checkedBy")); - map.put("checkedTeam", att.get("checkedTeam")); - map.put("checkedComment", att.get("checkedComment")); - map.put("checkedOn", att.get("checkedOn")); - attList.add(map); - } - valueToKeep.put(entry.getKey(), attList); - } else { - valueToKeep.put(entry.getKey(), entry.getValue()); + final List> releaseResources = paginationResult.getResources().stream() + .map(sw360Release -> wrapTException(() -> { + final Release embeddedRelease = restControllerHelper.convertToEmbeddedRelease(sw360Release); + final HalResource releaseResource = new HalResource<>(embeddedRelease); + if (transitive) { + projectService.addEmbeddedlinkedRelease(sw360Release, sw360User, releaseResource, + releaseService, releaseIdsInBranch); } - } - } - } - Map linksValeToKeep = new LinkedHashMap<>(); - linksValeToKeep.put("self", releaseLink); - valueToKeep.put("_links", linksValeToKeep); - modifiedList.add(valueToKeep); - } - return modifiedList; - } - - public List filterReleases(User sw360User, String filter, Set releaseIds) { - List releasesSrc = new ArrayList<>(); + return releaseResource; + })).collect(Collectors.toList()); - switch (filter) { - case "withSourceAttachment": - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - List sourceAttachments = sw360Release.getAttachments().stream() - .filter(attachment -> attachment.getAttachmentType() == AttachmentType.SOURCE || attachment.getAttachmentType() == AttachmentType.SOURCE_SELF) - .collect(Collectors.toList()); - Set sourceAttachmentsSet = new HashSet<>(sourceAttachments); - sw360Release.setAttachments(sourceAttachmentsSet); - return sourceAttachmentsSet.isEmpty() ? null : sw360Release; - })) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - break; - case "withoutSourceAttachment": - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - List withoutSourceAttachments = sw360Release.getAttachments().stream() - .filter(attachment -> attachment.getAttachmentType() != AttachmentType.SOURCE && attachment.getAttachmentType() != AttachmentType.SOURCE_SELF) - .collect(Collectors.toList()); - Set withoutSourceAttachmentsSet = new HashSet<>(withoutSourceAttachments); - sw360Release.setAttachments(withoutSourceAttachmentsSet); - return withoutSourceAttachmentsSet.isEmpty() ? null : sw360Release; - })) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - break; - case "withoutAttachment": - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - return sw360Release.getAttachments().isEmpty() ? sw360Release : null; - })) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - break; - case "withAttachment": - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - return sw360Release.getAttachments().isEmpty() ? null : sw360Release; - })) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - break; - case "withCliAttachment": - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - List cliAttachments = sw360Release.getAttachments().stream() - .filter(attachment -> attachment.getAttachmentType() == AttachmentType.COMPONENT_LICENSE_INFO_XML) - .collect(Collectors.toList()); - Set cliAttachmentsSet = new HashSet<>(cliAttachments); - sw360Release.setAttachments(cliAttachmentsSet); - return cliAttachmentsSet.isEmpty() ? null : sw360Release; - })) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - break; - default: - releasesSrc = releaseIds.stream().map(relId -> wrapTException(() -> { - final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); - releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); - return sw360Release; - })) - .collect(Collectors.toList()); - break; - } + CollectionModel resources; + if (releaseResources.size() == 0) { + resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources); + } - return releasesSrc; - } + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } + + + @Operation( + description = "Get all releases with ECC information of a single project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/releases/ecc", method = RequestMethod.GET) + public ResponseEntity>> getECCsOfReleases( + Pageable pageable, + HttpServletRequest request, + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Get the transitive ECC") + @RequestParam(value = "transitive", required = false) boolean transitive + ) throws TException, URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + List releases = new ArrayList<>(); + final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); + for (final String releaseId : releaseIds) { + Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); + releases.add(sw360Release); + } - private HalResource attachmentUsageReleases(Project sw360Project, List> releases, List> attachmentUsageMap) - throws TException { - ObjectMapper oMapper = new ObjectMapper(); - Map releaseIdToUsages = sw360Project.getReleaseIdToUsage(); - Map projectMap = oMapper.convertValue(sw360Project, Map.class); - Map resultMap = new HashMap<>(); - resultMap.put("linkedProjects", projectMap.get("linkedProjects")); - resultMap.put("releaseIdToUsage", projectMap.get("releaseIdToUsage")); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, releases, SW360Constants.TYPE_RELEASE); + final List> releaseResources = new ArrayList<>(); + for (Release rel : paginationResult.getResources()) { + Release embeddedRelease = restControllerHelper.convertToEmbeddedRelease(rel); + embeddedRelease.setEccInformation(rel.getEccInformation()); + final EntityModel releaseResource = EntityModel.of(embeddedRelease); + releaseResources.add(releaseResource); + } - Map releaseIdToUsage = (Map) resultMap.get("releaseIdToUsage"); - final ImmutableSet fieldsToRemove = ImmutableSet.of("setCreatedBy", "setCreatedOn", "setComment", "setReleaseRelation", "setMainlineState"); - if (releaseIdToUsage != null) { - for (Map.Entry entry : releaseIdToUsage.entrySet()) { - Map originalValue = (Map) entry.getValue(); - if (originalValue != null) { - for (String field : fieldsToRemove) { - originalValue.remove(field); - } - } + CollectionModel> resources; + if (releaseIds.size() == 0) { + resources = restControllerHelper.emptyPageResource(Release.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, releaseResources); } - } - HalResource halProject = new HalResource(resultMap); - if (releaseIdToUsages != null) { - restControllerHelper.addEmbeddedProjectAttachmentUsage(halProject, releases, attachmentUsageMap); + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); } - return halProject; - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - description = "Import SBOM in SPDX format.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/import/SBOM", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - public ResponseEntity importSBOM( - @Parameter(description = "Type of SBOM", example = "SPDX") - @RequestParam(value = "type", required = true) String type, - @Parameter(description = "SBOM file") - @RequestBody MultipartFile file - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Attachment attachment = null; - final RequestSummary requestSummary; - String projectId = null; - Map messageMap = new HashMap<>(); + @Operation( + description = "Get vulnerabilities of all projects including parent and directly linked projects.", + tags = {"Projects"} + ) - if (!(type.equalsIgnoreCase("SPDX") || type.equalsIgnoreCase("CycloneDX"))) { - return new ResponseEntity("Invalid SBOM file type. Only SPDX(.rdf/.xml) and CycloneDX(.json/.xml) files are supported.", - HttpStatus.BAD_REQUEST); - } + @RequestMapping(value = PROJECTS_URL + "/{id}/vulnerabilitySummary", method = RequestMethod.GET) + public ResponseEntity>> getAllVulnerabilities( + Pageable pageable, + HttpServletRequest request, + @PathVariable("id") String id) + throws TException, PaginationParameterException, ResourceClassNotFoundException, URISyntaxException { - try { - attachment = attachmentService.uploadAttachment(file, new Attachment(), sw360User); - } catch (IOException e) { - log.error("failed to upload attachment", e); - throw new RuntimeException("failed to upload attachment", e); - } + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); - if (type.equalsIgnoreCase("SPDX")) { - requestSummary = projectService.importSPDX(sw360User, attachment.getAttachmentContentId()); + List parentProjectVulnerabilities = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); - if (!(requestSummary.getRequestStatus() == RequestStatus.SUCCESS)) { - return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); - } - projectId = requestSummary.getMessage(); - } else { - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), ""); + Map> vulnerabilitiesMap = new HashMap<>(); + String parentProjectKey = sw360Project.getName() + " " + sw360Project.getVersion(); + vulnerabilitiesMap.put(parentProjectKey, parentProjectVulnerabilities); - if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { - return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); - } - else if(requestSummary.getRequestStatus() == RequestStatus.ACCESS_DENIED){ - return new ResponseEntity("You do not have sufficient permissions.", HttpStatus.UNAUTHORIZED); + if (!sw360Project.getLinkedProjects().isEmpty()) { + for (String linkedProjectId : sw360Project.getLinkedProjects().keySet()) { + Project linkedProject = projectService.getProjectForUserById(linkedProjectId, sw360User); + List linkedProjectVulnerabilities = vulnerabilityService.getVulnerabilitiesByProjectId(linkedProjectId, sw360User); + String linkedProjectKey = linkedProject.getName() + " " + linkedProject.getVersion(); + vulnerabilitiesMap.put(linkedProjectKey, linkedProjectVulnerabilities); + } } - String jsonMessage = requestSummary.getMessage(); - messageMap = new Gson().fromJson(jsonMessage, Map.class); - projectId = messageMap.get("projectId"); - - if (requestSummary.getRequestStatus() == RequestStatus.DUPLICATE) { - return new ResponseEntity("A project with same name and version already exists. The projectId is: " - + projectId, HttpStatus.CONFLICT); + List newList = new ArrayList<>(); + Optional projectVulnerabilityRating = wrapThriftOptionalReplacement(vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); + Map>> vulnerabilityIdToStatusHistory = projectVulnerabilityRating + .map(ProjectVulnerabilityRating::getVulnerabilityIdToReleaseIdToStatus).orElseGet(HashMap::new); + + + for (Map.Entry> entry : vulnerabilitiesMap.entrySet()) { + String projectName = entry.getKey(); + List vulnerabilities = entry.getValue(); + + for (VulnerabilityDTO vulnerability : vulnerabilities) { + String comment = "", action = "new"; + Map> vulRatingProj = vulnerabilityService.fillVulnerabilityMetadata(vulnerability, projectVulnerabilityRating); + vulnerability.setProjectRelevance(vulRatingProj.get(vulnerability.externalId).get(vulnerability.intReleaseId).toString()); + Map> relIdToCheckStatus = vulnerabilityIdToStatusHistory.get(vulnerability.externalId); + if(null != relIdToCheckStatus && relIdToCheckStatus.containsKey(vulnerability.intReleaseId)) { + List checkStatus = relIdToCheckStatus.get(vulnerability.intReleaseId); + comment = checkStatus.get(checkStatus.size()-1).getComment(); + action = checkStatus.get(checkStatus.size()-1).getProjectAction(); + } + vulnerability.setComment(comment); + vulnerability.setAction(action); + VulnerabilitySummary summ = new VulnerabilitySummary(); + summ.setProjectName(projectName); + summ.setExternalId(vulnerability.getExternalId()); + summ.setDescription(vulnerability.getDescription()); + summ.setTitle(vulnerability.getTitle()); + summ.setPriority(vulnerability.getPriority()); + summ.setPriorityToolTip(vulnerability.getPriorityToolTip()); + summ.setAction(vulnerability.getAction()); + summ.setComment(vulnerability.getComment()); + summ.setMatchedBy(vulnerability.getMatchedBy()); + summ.setUsedNeedle(vulnerability.getUsedNeedle()); + summ.setProjectRelevance(vulnerability.getProjectRelevance()); + summ.setIntReleaseId(vulnerability.getIntReleaseId()); + summ.setIntReleaseName(vulnerability.getIntReleaseName()); + newList.add(summ); + } } - } - Project project = projectService.getProjectForUserById(projectId, sw360User); - HalResource halResource = createHalProject(project, sw360User); - return new ResponseEntity>(halResource, HttpStatus.OK); - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Import SBOM on a project.", - description = "Import a SBOM on a project. Currently only CycloneDX(.xml/" + - ".json) files are supported.", - responses = { - @ApiResponse( - responseCode = "200", description = "Project successfully imported.", - content = { - @Content(mediaType = "application/json", - schema = @Schema(implementation = Project.class)) - } - ), - @ApiResponse( - responseCode = "409", description = "A project with same name and version already exists.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "A project with same name and version already exists. " + - "The projectId is: 376576" - )) - } - ), - }, - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/import/SBOM", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - public ResponseEntity importSBOMonProject( - @Parameter(description = "Project ID", example = "376576") - @PathVariable(value = "id", required = true) String id, - @Parameter(description = "SBOM file") - @RequestBody MultipartFile file - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Attachment attachment = null; - final RequestSummary requestSummary; - String projectId = null; - Map messageMap = new HashMap<>(); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, newList, SW360Constants.TYPE_VULNERABILITYSUMMARY); + + final List> vulResources = paginationResult.getResources().stream() + .map(sw360Vul -> wrapTException(() -> { + final VulnerabilitySummary embeddedVul = restControllerHelper.convertToEmbeddedVulnerabilitySumm(sw360Vul); + final HalResource vulResource = new HalResource<>(embeddedVul); + Link projectLink = linkTo(VulnerabilityController.class) + .slash("api/vulnerabilities/" + sw360Vul.getExternalId()).withSelfRel(); + vulResource.add(projectLink); + return vulResource; + })).collect(Collectors.toList()); + + CollectionModel resources; + if (vulResources.size() == 0) { + resources = restControllerHelper.emptyPageResource(Project.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, vulResources); + } - try { - attachment = attachmentService.uploadAttachment(file, new Attachment(), sw360User); - } catch (IOException e) { - log.error("failed to upload attachment", e); - throw new RuntimeException("failed to upload attachment", e); - } + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } + + @Operation( + description = "Get vulnerabilities of a single project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/vulnerabilities", method = RequestMethod.GET) + public ResponseEntity>> getVulnerabilitiesOfReleases( + Pageable pageable, + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "The priority of vulnerability.", + examples = {@ExampleObject(value = "1 - critical"), @ExampleObject(value = "2 - major")} + ) + @RequestParam(value = "priority") Optional priority, + @Parameter(description = "The relevance of project of the vulnerability.", + schema = @Schema(implementation = VulnerabilityRatingForProject.class) + ) + @RequestParam(value = "projectRelevance") Optional projectRelevance, + @Parameter(description = "The release Id of vulnerability.") + @RequestParam(value = "releaseId") Optional releaseId, + @Parameter(description = "The external Id of vulnerability.") + @RequestParam(value = "externalId") Optional externalId, + HttpServletRequest request + ) throws URISyntaxException, PaginationParameterException, ResourceClassNotFoundException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final List allVulnerabilityDTOs = vulnerabilityService.getVulnerabilitiesByProjectId(id, sw360User); + + Optional projectVulnerabilityRating = wrapThriftOptionalReplacement(vulnerabilityService.getProjectVulnerabilityRatingByProjectId(id, sw360User)); + Map>> vulnerabilityIdToStatusHistory = projectVulnerabilityRating + .map(ProjectVulnerabilityRating::getVulnerabilityIdToReleaseIdToStatus).orElseGet(HashMap::new); + + final List> vulnerabilityResources = new ArrayList<>(); + for (final VulnerabilityDTO vulnerabilityDTO : allVulnerabilityDTOs) { + String comment = "", action = ""; + Map> vulRatingProj = vulnerabilityService.fillVulnerabilityMetadata(vulnerabilityDTO, projectVulnerabilityRating); + vulnerabilityDTO.setProjectRelevance(vulRatingProj.get(vulnerabilityDTO.externalId).get(vulnerabilityDTO.intReleaseId).toString()); + Map> relIdToCheckStatus = vulnerabilityIdToStatusHistory.get(vulnerabilityDTO.externalId); + if(null != relIdToCheckStatus && relIdToCheckStatus.containsKey(vulnerabilityDTO.intReleaseId)) { + List checkStatus = relIdToCheckStatus.get(vulnerabilityDTO.intReleaseId); + comment = checkStatus.get(checkStatus.size()-1).getComment(); + action = checkStatus.get(checkStatus.size()-1).getProjectAction(); + } + vulnerabilityDTO.setComment(comment); + vulnerabilityDTO.setAction(action); + final EntityModel vulnerabilityDTOEntityModel = EntityModel.of(vulnerabilityDTO); + vulnerabilityResources.add(vulnerabilityDTOEntityModel); + } - requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id); + List priorityList = priority.isPresent() ? Lists.newArrayList(priority.get().split(",")) : Lists.newArrayList(); + List projectRelevanceList = projectRelevance.isPresent() ? Lists.newArrayList(projectRelevance.get().split(",")) : Lists.newArrayList(); + final List> vulnResources = vulnerabilityResources.stream() + .filter(vulRes -> projectRelevance.isEmpty() || projectRelevanceList.contains(vulRes.getContent().getProjectRelevance())) + .filter(vulRes -> priority.isEmpty() || priorityList.contains(vulRes.getContent().getPriority())) + .filter(vulRes -> !releaseId.isPresent() || vulRes.getContent().getIntReleaseId().equals(releaseId.get())) + .filter(vulRes -> !externalId.isPresent() || vulRes.getContent().getExternalId().equals(externalId.get())) + .collect(Collectors.toList()); + + List vulDtos = vulnResources.stream().map(res -> res.getContent()).collect(Collectors.toList()); + PaginationResult paginationResult = restControllerHelper.createPaginationResult(request, pageable, vulDtos, SW360Constants.TYPE_VULNERABILITYDTO); + List> paginatedVulnResources = Lists.newArrayList(); + for (VulnerabilityDTO vd: paginationResult.getResources()) { + EntityModel vDTOEntityModel = EntityModel.of(vd); + paginatedVulnResources.add(vDTOEntityModel); + } + CollectionModel resources; + if (vulnResources.size() == 0) { + resources = restControllerHelper.emptyPageResource(VulnerabilityDTO.class, paginationResult); + } else { + resources = restControllerHelper.generatePagesResource(paginationResult, paginatedVulnResources); + } - if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { - return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); - }else if(requestSummary.getRequestStatus() == RequestStatus.ACCESS_DENIED){ - return new ResponseEntity("You do not have sufficient permissions.", HttpStatus.UNAUTHORIZED); + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); } - String jsonMessage = requestSummary.getMessage(); - messageMap = new Gson().fromJson(jsonMessage, Map.class); - projectId = messageMap.get("projectId"); - if (requestSummary.getRequestStatus() == RequestStatus.DUPLICATE) { - return new ResponseEntity( - "A project with same name and version already exists. The projectId is: " + projectId, - HttpStatus.CONFLICT); - }else if (requestSummary.getRequestStatus() == RequestStatus.FAILED_SANITY_CHECK){ - return new ResponseEntity( - "Project name or version present in SBOM metadata tag is not same as the current SW360 project!", - HttpStatus.BAD_REQUEST); - } + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Link releases to the project with usage.", + description = "Pass a map of release id to usage to be linked as request body.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/release/{releaseId}", method = RequestMethod.PATCH) + public ResponseEntity> patchProjectReleaseUsage( + @Parameter(description = "Project ID.") @PathVariable("id") String id, + @Parameter(description = "Release ID.") @PathVariable("releaseId") String releaseId, + @Parameter(description = "Map of release id to usage.") + @RequestBody ProjectReleaseRelationship requestBodyProjectReleaseRelationship,@RequestParam(value = "comment", required = false) String comment) throws TException { - Project project = projectService.getProjectForUserById(projectId, sw360User); - HalResource halResource = createHalProject(project, sw360User); - return new ResponseEntity>(halResource, HttpStatus.OK); - } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - @Operation( - summary = "Get a single project with dependencies network.", - responses = { - @ApiResponse( - responseCode = "200", description = "Project successfully imported.", - content = { - @Content(mediaType = "application/json", - schema = @Schema(implementation = ProjectDTO.class)) - } - ), - @ApiResponse( - responseCode = "500", description = "Project release relationship is not enabled.", - content = { - @Content(mediaType = "application/json", - examples = @ExampleObject( - value = "Please enable flexible project release relationship " + - "configuration to use this function (enable.flexible.project.release.relationship = true)" - )) - } - ), - }, - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/network/{id}", method = RequestMethod.GET) - public ResponseEntity getProjectWithNetwork( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id - ) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); - } + Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); - HalResource projectDTOHalResource = createHalProjectDTO(sw360Project, sw360User); - return new ResponseEntity<>(projectDTOHalResource, HttpStatus.OK); - } + ProjectReleaseRelationship updatedProjectReleaseRelationship = projectService + .updateProjectReleaseRelationship(releaseIdToUsage, requestBodyProjectReleaseRelationship, releaseId); - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Create a project with dependencies network.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL+ "/network", method = RequestMethod.POST) - public ResponseEntity createProjectWithNetwork( - @Parameter(description = "Project with `dependencyNetwork` set.", - schema = @Schema(implementation = Project.class)) - @RequestBody Map reqBodyMap - ) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); - } + if (!restControllerHelper.isWriteActionAllowed(sw360Project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } + sw360User.setCommentMadeDuringModerationRequest(comment); + RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, sw360User); + if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project project = convertToProject(reqBodyMap); - project.unsetReleaseIdToUsage(); - try { - addOrPatchDependencyNetworkToProject(project, reqBodyMap, ProjectOperation.CREATE); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - return ResponseEntity.badRequest().body("Invalid dependency network format"); - } catch (SW360Exception sw360Exception) { - log.error(sw360Exception.getWhy()); - return ResponseEntity.badRequest().body(sw360Exception.getWhy()); + EntityModel updatedProjectReleaseRelationshipResource = EntityModel.of( + updatedProjectReleaseRelationship); + return new ResponseEntity<>(updatedProjectReleaseRelationshipResource, HttpStatus.OK); } - projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(project, sw360User); - - project = projectService.createProject(project, sw360User); - URI location = ServletUriComponentsBuilder - .fromCurrentRequest().path("/{id}") - .buildAndExpand(project.getId()).toUri(); - - HalResource halResource = createHalProjectDTO(project, sw360User); - return ResponseEntity.created(location).body(halResource); - } + public ProjectVulnerabilityRating updateProjectVulnerabilityRatingFromRequest(Optional projectVulnerabilityRatings, List vulDtoList, String projectId, User sw360User) { + Function fillVulnerabilityCheckStatus = vulDto -> { + return new VulnerabilityCheckStatus().setCheckedBy(sw360User.getEmail()) + .setCheckedOn(SW360Utils.getCreatedOn()) + .setVulnerabilityRating(ThriftEnumUtils.stringToEnum(vulDto.getProjectRelevance(), VulnerabilityRatingForProject.class)) + .setComment(vulDto.getComment() == null ? "" : vulDto.getComment()).setProjectAction(vulDto.getAction()); - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Update a project with dependencies network.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/network/{id}", method = RequestMethod.PATCH) - public ResponseEntity patchProjectWithNetwork( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Project with `dependencyNetwork` set.", - schema = @Schema(implementation = Project.class)) - @RequestBody Map reqBodyMap - ) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); - } + }; - User user = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, user); - Project updateProject = convertToProject(reqBodyMap); - updateProject.unsetReleaseIdToUsage(); - sw360Project.unsetReleaseIdToUsage(); + ProjectVulnerabilityRating projectVulnerabilityRating = projectVulnerabilityRatings.orElse( + new ProjectVulnerabilityRating() + .setProjectId(projectId) + .setVulnerabilityIdToReleaseIdToStatus(new HashMap<>())); - try { - addOrPatchDependencyNetworkToProject(updateProject, reqBodyMap, ProjectOperation.UPDATE); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - return ResponseEntity.badRequest().body("Invalid dependency network format"); - } catch (SW360Exception sw360Exception) { - log.error(sw360Exception.getWhy()); - return ResponseEntity.badRequest().body(sw360Exception.getWhy()); + if (!projectVulnerabilityRating.isSetVulnerabilityIdToReleaseIdToStatus()) { + projectVulnerabilityRating.setVulnerabilityIdToReleaseIdToStatus(new HashMap<>()); + } + Map>> vulnerabilityIdToReleaseIdToStatus = projectVulnerabilityRating.getVulnerabilityIdToReleaseIdToStatus(); + + String[] vulnerabilityIds = vulDtoList.stream().map(dto -> dto.getExternalId()).toArray(String[]::new); + String[] releaseIds = vulDtoList.stream().map(dto -> dto.getIntReleaseId()).toArray(String[]::new); + VulnerabilityCheckStatus[] vulStatusCheck = vulDtoList.stream().map(dt -> fillVulnerabilityCheckStatus.apply(dt)).toArray(VulnerabilityCheckStatus[]::new); + + for (int i = 0; i < vulnerabilityIds.length; i++) { + String vulnerabilityId = vulnerabilityIds[i]; + String releaseId = releaseIds[i]; + + Map> releaseIdToStatus = vulnerabilityIdToReleaseIdToStatus.computeIfAbsent(vulnerabilityId, k -> new HashMap<>()); + List vulnerabilityCheckStatusHistory = releaseIdToStatus.computeIfAbsent(releaseId, k -> new ArrayList<>()); + VulnerabilityCheckStatus statustoBeUpdated = vulStatusCheck[i]; + if (!vulnerabilityCheckStatusHistory.isEmpty()) { + VulnerabilityCheckStatus latestStatusFromHistory = vulnerabilityCheckStatusHistory + .get(vulnerabilityCheckStatusHistory.size() - 1); + if (StringUtils.isEmpty(statustoBeUpdated.getComment())) { + statustoBeUpdated.setComment(latestStatusFromHistory.getComment()); + } + if (StringUtils.isEmpty(statustoBeUpdated.getProjectAction())) { + statustoBeUpdated.setProjectAction(latestStatusFromHistory.getProjectAction()); + } + if (Objects.isNull(statustoBeUpdated.getVulnerabilityRating())) { + statustoBeUpdated.setVulnerabilityRating(latestStatusFromHistory.getVulnerabilityRating()); + } + } + vulnerabilityCheckStatusHistory.add(statustoBeUpdated); + } + + return projectVulnerabilityRating; } - sw360Project = this.restControllerHelper.updateProject(sw360Project, updateProject, reqBodyMap, mapOfProjectFieldsToRequestBody); - projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(sw360Project, user); + @Operation( + description = "Get license of releases of a single project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenses", method = RequestMethod.GET) + public ResponseEntity>> getLicensesOfReleases( + @Parameter(description = "Project ID.") + @PathVariable("id") String id + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project project = projectService.getProjectForUserById(id, sw360User); + final List> licenseResources = new ArrayList<>(); + final Set allLicenseIds = new HashSet<>(); + + final Set releaseIdToUsage = project.getReleaseIdToUsage().keySet(); + for (final String releaseId : releaseIdToUsage) { + final Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); + final Set licenseIds = sw360Release.getMainLicenseIds(); + if (licenseIds != null && !licenseIds.isEmpty()) { + allLicenseIds.addAll(licenseIds); + } + } + for (final String licenseId : allLicenseIds) { + final License sw360License = licenseService.getLicenseById(licenseId); + final License embeddedLicense = restControllerHelper.convertToEmbeddedLicense(sw360License); + final EntityModel licenseResource = EntityModel.of(embeddedLicense); + licenseResources.add(licenseResource); + } - RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, user); - if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { - return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); - } - HalResource projectDTOHalResource = createHalProjectDTO(sw360Project, user); - return new ResponseEntity<>(projectDTOHalResource, HttpStatus.OK); - } + final CollectionModel> resources = restControllerHelper.createResources(licenseResources); + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } + + @Operation( + summary = "Download license info for the project.", + description = "Set the request parameter `&template=` for variant `REPORT` to choose " + + "specific template.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenseinfo", method = RequestMethod.GET) + public void downloadLicenseInfo( + @Parameter(description = "Project ID.", example = "376576") + @PathVariable("id") String id, + @Parameter(description = "Output generator class", + schema = @Schema(type = "string", + allowableValues = {"DocxGenerator", "XhtmlGenerator", + "TextGenerator"} + )) + @RequestParam("generatorClassName") String generatorClassName, + @Parameter(description = "Variant of the report", + schema = @Schema(implementation = OutputFormatVariant.class)) + @RequestParam("variant") String variant, + @Parameter(description = "The external Ids of the project", example = "376577") + @RequestParam(value = "externalIds", required = false) String externalIds, + @RequestParam(value = "template", required = false) String template, + @Parameter(description = "Generate license info including all attachments of the linked releases") + @RequestParam(value = "includeAllAttachments", required = false ) boolean includeAllAttachments, + @Parameter(description = "Exclude release version from the license info file") + @RequestParam(value = "excludeReleaseVersion", required = false, defaultValue = "false") boolean excludeReleaseVersion, + HttpServletResponse response + ) throws TException, IOException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + List mappedProjectLinks = new ArrayList<>(); + + if (includeAllAttachments) { + mappedProjectLinks = projectService.createLinkedProjects(sw360Project, + projectService.filterAndSortAllAttachments(SW360Constants.INITIAL_LICENSE_INFO_ATTACHMENT_TYPES), true, sw360User); + } else { + mappedProjectLinks = projectService.createLinkedProjects(sw360Project, + projectService.filterAndSortAttachments(SW360Constants.LICENSE_INFO_ATTACHMENT_TYPES), true, sw360User); + } - @Override - public RepositoryLinksResource process(RepositoryLinksResource resource) { - resource.add(linkTo(ProjectController.class).slash("api" + PROJECTS_URL).withRel("projects")); - return resource; - } + List attchmntUsg = attachmentService.getAttachemntUsages(id); - private HalResource createHalLicenseClearing(Project sw360Project, List> releases) { - Project sw360 = new Project(); - Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); - sw360.setReleaseIdToUsage(sw360Project.getReleaseIdToUsage()); - sw360.setLinkedProjects(sw360Project.getLinkedProjects()); - sw360.unsetState(); - sw360.unsetProjectType(); - sw360.unsetVisbility(); - sw360.unsetSecurityResponsibles(); - HalResource halProject = new HalResource<>(sw360); - - if (releaseIdToUsage != null) { - restControllerHelper.addEmbeddedProjectReleases(halProject, releases); - } - return halProject; - } + Map> releaseIdToExcludedLicenses = attchmntUsg.stream() + .collect(Collectors.toMap(AttachmentUsage::getOwner, + x -> x.getUsageData().getLicenseInfo().getExcludedLicenseIds(), (li1, li2) -> li1)); - private HalResource createHalProject(Project sw360Project, User sw360User) throws TException { - HalResource halProject = new HalResource<>(sw360Project); - User projectCreator = restControllerHelper.getUserByEmail(sw360Project.getCreatedBy()); - restControllerHelper.addEmbeddedUser(halProject, projectCreator, CREATED_BY); + Map usedAttachmentContentIds = attchmntUsg.stream() + .collect(Collectors.toMap(AttachmentUsage::getAttachmentContentId, attUsage -> { + if (attUsage.isSetUsageData() + && attUsage.getUsageData().getSetField().equals(UsageData._Fields.LICENSE_INFO)) { + return Boolean.valueOf(attUsage.getUsageData().getLicenseInfo().isIncludeConcludedLicense()); + } + return Boolean.FALSE; + }, (li1, li2) -> li1)); - Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); - if (releaseIdToUsage != null) { - restControllerHelper.addEmbeddedReleases(halProject, releaseIdToUsage.keySet(), releaseService, sw360User); - } + final Map> selectedReleaseAndAttachmentIds = new HashMap<>(); + final Map> excludedLicensesPerAttachments = new HashMap<>(); - Map linkedProjects = sw360Project.getLinkedProjects(); - if (linkedProjects != null) { - restControllerHelper.addEmbeddedProject(halProject, linkedProjects.keySet(), projectService, sw360User); - } - if (sw360Project.getModerators() != null) { - Set moderators = sw360Project.getModerators(); - restControllerHelper.addEmbeddedModerators(halProject, moderators); - } + mappedProjectLinks.forEach(projectLink -> wrapTException(() -> + projectLink.getLinkedReleases().stream().filter(ReleaseLink::isSetAttachments).forEach(releaseLink -> { + String releaseLinkId = releaseLink.getId(); + Set excludedLicenseIds = releaseIdToExcludedLicenses.get(Source.releaseId(releaseLinkId)); - if (sw360Project.getAttachments() != null) { - restControllerHelper.addEmbeddedAttachments(halProject, sw360Project.getAttachments()); - } + if (!selectedReleaseAndAttachmentIds.containsKey(releaseLinkId)) { + selectedReleaseAndAttachmentIds.put(releaseLinkId, new HashMap<>()); + } + final List attachments = releaseLink.getAttachments(); + Release release = componentService.getReleaseById(releaseLinkId, sw360User); + for (final Attachment attachment : attachments) { + String attachemntContentId = attachment.getAttachmentContentId(); + if (includeAllAttachments) { + selectedReleaseAndAttachmentIds.get(releaseLinkId).put(attachemntContentId, + false); + } else { + if (usedAttachmentContentIds.containsKey(attachemntContentId)) { + boolean includeConcludedLicense = usedAttachmentContentIds.get(attachemntContentId); + List licenseInfoParsingResult = licenseInfoService + .getLicenseInfoForAttachment(release, sw360User, attachemntContentId, includeConcludedLicense); + excludedLicensesPerAttachments.put(attachemntContentId, getExcludedLicenses(excludedLicenseIds, licenseInfoParsingResult)); + selectedReleaseAndAttachmentIds.get(releaseLinkId).put(attachemntContentId, includeConcludedLicense); + } + } + } + }))); + + final String projectName = sw360Project.getName(); + final String projectVersion = sw360Project.getVersion(); + final String timestamp = SW360Utils.getCreatedOnTime().replaceAll("\\s", "_").replace(":", "_"); + String outputGeneratorClassNameWithVariant = generatorClassName+"::"+variant; + final OutputFormatInfo outputFormatInfo = licenseInfoService.getOutputFormatInfoForGeneratorClass(generatorClassName); + final String filename = String.format("%s-%s%s-%s.%s", Strings.nullToEmpty(variant).equals("DISCLOSURE") ? "LicenseInfo" : "ProjectClearingReport", projectName, + StringUtils.isBlank(projectVersion) ? "" : "-" + projectVersion, timestamp, + outputFormatInfo.getFileExtension()); + + String fileName = ""; + if (CommonUtils.isNotNullEmptyOrWhitespace(template) && CommonUtils.isNotNullEmptyOrWhitespace(REPORT_FILENAME_MAPPING)) { + Map orgToTemplate = Arrays.stream(REPORT_FILENAME_MAPPING.split(",")) + .collect(Collectors.toMap(k -> k.split(":")[0], v -> v.split(":")[1])); + fileName = orgToTemplate.get(template); + } - if(sw360Project.getLeadArchitect() != null) { - restControllerHelper.addEmbeddedLeadArchitect(halProject, sw360Project.getLeadArchitect()); + final LicenseInfoFile licenseInfoFile = licenseInfoService.getLicenseInfoFile(sw360Project, sw360User, + outputGeneratorClassNameWithVariant, selectedReleaseAndAttachmentIds, excludedLicensesPerAttachments, + externalIds, fileName, excludeReleaseVersion); + byte[] byteContent = licenseInfoFile.bufferForGeneratedOutput().array(); + response.setContentType(outputFormatInfo.getMimeType()); + response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)); + FileCopyUtils.copy(byteContent, response.getOutputStream()); } - if (sw360Project.getContributors() != null) { - Set contributors = sw360Project.getContributors(); - restControllerHelper.addEmbeddedContributors(halProject, contributors); - } - if (sw360Project.getVendor() != null) { - Vendor vendor = sw360Project.getVendor(); - Vendor vendorHalResource = restControllerHelper.convertToEmbeddedVendor(vendor); - halProject.addEmbeddedResource("sw360:vendors", vendorHalResource); - sw360Project.setVendor(null); + @Operation( + description = "Get all attachment information of a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/attachments", method = RequestMethod.GET) + public ResponseEntity>> getProjectAttachments( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, @RequestParam(value = "comment", required = false) String comment + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + final CollectionModel> resources = attachmentService.getResourcesFromList(sw360Project.getAttachments()); + return new ResponseEntity<>(resources, HttpStatus.OK); } - if (sw360Project.getPackageIdsSize() > 0) { - restControllerHelper.addEmbeddedPackages(halProject, sw360Project.getPackageIds(), packageService); - } + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + description = "Update and attachment usage for project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/attachment/{attachmentId}", method = RequestMethod.PATCH) + public ResponseEntity> patchProjectAttachmentInfo( + @Parameter(description = "Project ID.") @PathVariable("id") String id, + @Parameter(description = "Attachment ID.") @PathVariable("attachmentId") String attachmentId, + @RequestBody Attachment attachmentData, @RequestParam(value = "comment", required = false) String comment) throws TException { + + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + Set attachments = sw360Project.getAttachments(); + sw360User.setCommentMadeDuringModerationRequest(comment); + Attachment updatedAttachment = attachmentService.updateAttachment(attachments, attachmentData, attachmentId, sw360User); + if (!restControllerHelper.isWriteActionAllowed(sw360Project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } + RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, sw360User); + if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + EntityModel attachmentResource = EntityModel.of(updatedAttachment); + return new ResponseEntity<>(attachmentResource, HttpStatus.OK); + } + + + @Operation( + summary = "Download an attachment of a project", + description = "Download an attachment of a project. Set the Accept-Header `application/*`. " + + "Only this Accept-Header is supported.", + responses = @ApiResponse( + content = {@Content(mediaType = "application/*")} + ), + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments/{attachmentId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public void downloadAttachmentFromProject( + @Parameter(description = "Project ID.") + @PathVariable("projectId") String projectId, + @Parameter(description = "Attachment ID.") + @PathVariable("attachmentId") String attachmentId, + HttpServletResponse response + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project project = projectService.getProjectForUserById(projectId, sw360User); + this.attachmentService.downloadAttachmentWithContext(project, attachmentId, response, sw360User); + } + + @Operation( + description = "Download clearing reports as a zip.", + responses = @ApiResponse( + content = {@Content(mediaType = "application/zip")} + ), + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments/clearingReports", method = RequestMethod.GET, produces = "application/zip") + public void downloadClearingReports( + @Parameter(description = "Project ID.") + @PathVariable("projectId") String projectId, + HttpServletResponse response + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project project = projectService.getProjectForUserById(projectId, sw360User); + final String filename = "Clearing-Reports-" + project.getName() + ".zip"; - return halProject; - } + final Set attachments = project.getAttachments(); + final Set clearingAttachments = new HashSet<>(); + for (final Attachment attachment : attachments) { + if (attachment.getAttachmentType().equals(AttachmentType.CLEARING_REPORT)) { + clearingAttachments.add(attachmentService.getAttachmentContent(attachment.getAttachmentContentId())); + } + } - private RequestStatus addOrPatchReleasesToProject(String id, Object releasesInRequestBody, boolean patch) - throws URISyntaxException, TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project project = projectService.getProjectForUserById(id, sw360User); - Map releaseIdToUsage = new HashMap<>(); - if (patch) { - releaseIdToUsage = project.getReleaseIdToUsage(); + try (InputStream attachmentStream = attachmentService.getStreamToAttachments(clearingAttachments, sw360User, project)) { + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)); + FileCopyUtils.copy(attachmentStream, response.getOutputStream()); + } catch (final TException | IOException e) { + log.error(e.getMessage()); + } } - if (releasesInRequestBody instanceof List) { - List releasesAsList = (List) releasesInRequestBody; - for (String release : releasesAsList) { - URI releaseURI = new URI(release.toString()); - String path = releaseURI.getPath(); - String releaseId = path.substring(path.lastIndexOf('/') + 1); - releaseIdToUsage.put(releaseId, - new ProjectReleaseRelationship(ReleaseRelationship.CONTAINED, MainlineState.OPEN)); - } - } else if (releasesInRequestBody instanceof Map) { - Map releaseAsMap = (Map) releasesInRequestBody; - for (Entry entry : releaseAsMap.entrySet()) { - String releaseId = entry.getKey(); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.registerModule(sw360Module); - ProjectReleaseRelationship requestBodyProjectReleaseRelationship = mapper.convertValue(entry.getValue(), - ProjectReleaseRelationship.class); - ProjectReleaseRelationship actualProjectReleaseRelationship = null; - - if (patch && releaseIdToUsage.containsKey(releaseId)) { - actualProjectReleaseRelationship = releaseIdToUsage.get(releaseId); - } else { - actualProjectReleaseRelationship = new ProjectReleaseRelationship(ReleaseRelationship.CONTAINED, - MainlineState.OPEN).setCreatedBy(sw360User.getEmail()) - .setCreatedOn(SW360Utils.getCreatedOn()); + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + description = "Update a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}", method = RequestMethod.PATCH) + public ResponseEntity> patchProject( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Updated values", schema = @Schema(implementation = Project.class)) + @RequestBody Map reqBodyMap + ) throws TException { + User user = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, user); + Project updateProject = convertToProject(reqBodyMap); + updateProject.unsetReleaseRelationNetwork(); + String comment = (String) reqBodyMap.get("comment"); + user.setCommentMadeDuringModerationRequest(comment); + + if (!restControllerHelper.isWriteActionAllowed(sw360Project, user) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } else { + sw360Project = this.restControllerHelper.updateProject(sw360Project, updateProject, reqBodyMap, + mapOfProjectFieldsToRequestBody); + if (SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP + && updateProject.getReleaseIdToUsage() != null) { + sw360Project.unsetReleaseRelationNetwork(); + projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(sw360Project, user); } - - restControllerHelper.updateProjectReleaseRelationship(actualProjectReleaseRelationship, - requestBodyProjectReleaseRelationship); - releaseIdToUsage.put(releaseId, actualProjectReleaseRelationship); + RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, user); + HalResource userHalResource = createHalProject(sw360Project, user); + if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + return new ResponseEntity<>(userHalResource, HttpStatus.OK); } - } else { - throw new HttpMessageNotReadableException( - "Request body should be List of valid release id or map of release id to usage"); } - project.setReleaseIdToUsage(releaseIdToUsage); - return projectService.updateProject(project, sw360User); - } - private RequestStatus linkOrUnlinkPackages(String id, Set packagesInRequestBody, boolean link) - throws URISyntaxException, TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project project = projectService.getProjectForUserById(id, sw360User); - Set packageIds = new HashSet<>(); - if (!CommonUtils.isNullOrEmptyCollection(project.getPackageIds())) { - packageIds = project.getPackageIds(); - } - if (link) { - packageIds.addAll(packagesInRequestBody); - } else { - packageIds.removeAll(packagesInRequestBody); - } + @Operation( + description = "Add attachments to a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{projectId}/attachments", method = RequestMethod.POST, consumes = {"multipart/mixed", "multipart/form-data"}) + public ResponseEntity addAttachmentToProject( + @Parameter(description = "Project ID.") @PathVariable("projectId") String projectId, + @Parameter(description = "File to attach") @RequestPart("file") MultipartFile file, + @Parameter(description = "Attachment description") @RequestPart("attachment") Attachment newAttachment, HttpServletRequest request, + @RequestParam(value = "comment", required = false) String comment) + throws TException, IOException { - project.setPackageIds(packageIds); - return projectService.updateProject(project, sw360User); - } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project project = projectService.getProjectForUserById(projectId, sw360User); - private HalResource createHalProjectResourceWithAllDetails(Project sw360Project, User sw360User) { - HalResource halProject = new HalResource<>(sw360Project); - halProject.addEmbeddedResource(CREATED_BY, sw360Project.getCreatedBy()); + Attachment attachment; + 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); + } + project.addToAttachments(attachment); + System.out.println(request.getReader().lines()); + if (!restControllerHelper.isWriteActionAllowed(project, sw360User) && comment == null) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } + sw360User.setCommentMadeDuringModerationRequest(comment); + RequestStatus updateProjectStatus = projectService.updateProject(project, sw360User); + if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); + } + HalResource halResource = createHalProject(project, sw360User); + HttpStatus status = HttpStatus.OK; + return new ResponseEntity<>(halResource, status); + } + + + @Operation( + summary = "Get all projects corresponding to external ids.", + description = "The request parameter supports MultiValueMap (allows to add duplicate keys with different " + + "values). It's possible to search for projects only by the external id key by leaving the value.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/searchByExternalIds", method = RequestMethod.GET) + public ResponseEntity searchByExternalIds( + @Parameter(description = "External ID map for filter.", + example = "{\"project-ext\": \"515432\", \"project-ext\": \"7657\", \"portal-id\": \"13319-XX3\"}" + ) + HttpServletRequest request + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + String queryString = request.getQueryString(); + return restControllerHelper.searchByExternalIds(queryString, projectService, sw360User); + } + + @Operation( + description = "Get all the projects where the project is used.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/usedBy/{id}", method = RequestMethod.GET) + public ResponseEntity>> getUsedByProjectDetails( + @Parameter(description = "Project ID to search.") + @PathVariable("id") String id + ) throws TException { + User user = restControllerHelper.getSw360UserFromAuthentication(); + Set sw360Projects = projectService.searchLinkingProjects(id, user); - Set packageIds = sw360Project.getPackageIds(); + List> projectResources = new ArrayList<>(); + sw360Projects.forEach(p -> { + Project embeddedProject = restControllerHelper.convertToEmbeddedProject(p); + projectResources.add(EntityModel.of(embeddedProject)); + }); - if (packageIds != null) { - for (String id : sw360Project.getPackageIds()) { - Link packageLink = linkTo(ProjectController.class) - .slash("api" + PackageController.PACKAGES_URL + "/" + id).withRel("packages"); - halProject.add(packageLink); + CollectionModel> resources = restControllerHelper.createResources(projectResources); + HttpStatus status = resources == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(resources, status); + } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Save attachment usages", + description = "Pass an array of string in request body.", + tags = {"Projects"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "AttachmentUsages Saved Successfully.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"AttachmentUsages Saved Successfully\"}" + )) + }), + @ApiResponse( + responseCode = "403", description = "No write permission for project.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"No write permission for project\"}" + )) + }), + @ApiResponse( + responseCode = "409", description = "Not a valid attachment type OR release does not belong to project.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Not a valid attachment type OR release does not belong to project\"}" + )) + } + ), + @ApiResponse( + responseCode = "500", description = "Internal Server Error.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Saving attachment usages for project 123456 failed\"}" + )) + } + ) + }) + @RequestMapping(value = PROJECTS_URL + "/{id}/saveAttachmentUsages", method = RequestMethod.POST) + public ResponseEntity saveAttachmentUsages( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Map of key-value pairs where each key is associated with a list of strings.", + schema = @Schema( + example = """ + { + "selected": [ + "4427a8e723ad405db63f75170ef240a2_sourcePackage_5c5d6f54ac6a4b33bcd3c5d3a8fefc43", "value2" + ], + "deselected": [ + "de213309ba0842ac8a7251bf27ea8f36_manuallySet_eec66c3465f64f0292dfc2564215c681", "value2" + ], + "selectedConcludedUsages": [ + "de213309ba0842ac8a7251bf27ea8f36_licenseInfo_eec66c3465f64f0292dfc2564215c681", "value2" + ], + "deselectedConcludedUsages": [ + "ade213309ba0842ac8a7251bf27ea8f36_licenseInfo_aeec66c3465f64f0292dfc2564215c681", "value2" + ] + } + """ + ) + ) + @RequestBody Map> allUsages + ) throws TException { + final User user = restControllerHelper.getSw360UserFromAuthentication(); + final Project project = projectService.getProjectForUserById(id, user); + try { + if (PermissionUtils.makePermission(project, user).isActionAllowed(RequestedAction.WRITE)) { + Source usedBy = Source.projectId(id); + List selectedUsages = new ArrayList<>(); + List deselectedUsages = new ArrayList<>(); + List selectedConcludedUsages = new ArrayList<>(); + List deselectedConcludedUsages = new ArrayList<>(); + List changedUsages = new ArrayList<>(); + for (Map.Entry> entry : allUsages.entrySet()) { + String key = entry.getKey(); + List list = entry.getValue(); + switch (key) { + case "selected" -> selectedUsages.addAll(list); + case "deselected" -> deselectedUsages.addAll(list); + case "selectedConcludedUsages" -> selectedConcludedUsages.addAll(list); + case "deselectedConcludedUsages" -> deselectedConcludedUsages.addAll(list); + } + } + Set totalReleaseIds = projectService.getReleaseIds(id, user, true); + changedUsages.addAll(selectedUsages); + changedUsages.addAll(deselectedUsages); + boolean valid = projectService.validate(changedUsages, user, releaseService, totalReleaseIds); + if (!valid) { + return new ResponseEntity<>("Not a valid attachment type OR release does not belong to project", HttpStatus.CONFLICT); + } + List allUsagesByProject = projectService.getUsedAttachments(usedBy, null); + List savedUsages = projectService.savedUsages(allUsagesByProject); + savedUsages.removeAll(deselectedUsages); + deselectedUsages.addAll(selectedUsages); + selectedUsages.addAll(savedUsages); + deselectedConcludedUsages.addAll(selectedConcludedUsages); + List deselectedUsagesFromRequest = projectService.deselectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id); + List selectedUsagesFromRequest = projectService.selectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id); + List usagesToDelete = allUsagesByProject.stream() + .filter(usage -> deselectedUsagesFromRequest.stream() + .anyMatch(projectService.isUsageEquivalent(usage))) + .collect(Collectors.toList()); + if (!usagesToDelete.isEmpty()) { + projectService.deleteAttachmentUsages(usagesToDelete); + } + List allUsagesByProjectAfterCleanUp = projectService.getUsedAttachments(usedBy, null); + List usagesToCreate = selectedUsagesFromRequest.stream() + .filter(usage -> allUsagesByProjectAfterCleanUp.stream() + .noneMatch(projectService.isUsageEquivalent(usage))) + .collect(Collectors.toList()); + + if (!usagesToCreate.isEmpty()) { + projectService.makeAttachmentUsages(usagesToCreate); + } + return new ResponseEntity<>("AttachmentUsages Saved Successfully", HttpStatus.CREATED); + } else { + return new ResponseEntity<>("No write permission for project", HttpStatus.FORBIDDEN); + } + } catch (TException e) { + return new ResponseEntity<>("Saving attachment usages for project " + id + " failed", HttpStatus.INTERNAL_SERVER_ERROR); } } - List obsoleteFields = List.of("homepage", "wiki"); - for (Entry field : mapOfFieldsTobeEmbedded.entrySet()) { - if (Project._Fields.EXTERNAL_URLS.equals(field.getKey())) { - Map externalUrls = CommonUtils - .nullToEmptyMap((Map) sw360Project.getFieldValue(field.getKey())); - restControllerHelper.addEmbeddedFields(obsoleteFields.get(0), - externalUrls.get(obsoleteFields.get(0)) == null ? "" : externalUrls.get(obsoleteFields.get(0)), - halProject); - restControllerHelper.addEmbeddedFields(obsoleteFields.get(1), - externalUrls.get(obsoleteFields.get(1)) == null ? "" : externalUrls.get(obsoleteFields.get(1)), - halProject); + @Operation( + description = "Get all attachmentUsages of the projects.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/attachmentUsage", method = RequestMethod.GET) + public ResponseEntity attachmentUsages( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter( + description = "filtering attachmentUsages", + schema = @Schema(allowableValues = { + "withSourceAttachment", "withoutSourceAttachment", "withoutAttachment", + "withAttachment", "withCliAttachment"}) + ) + @RequestParam(value = "filter", required = false) String filter, + @Parameter(description = "Get the transitive releases.") + @RequestParam(value = "transitive", required = true) boolean transitive + ) throws TException { + + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); + final Set releaseIds = projectService.getReleaseIds(id, sw360User, transitive); + List releases = null; + if (filter != null) { + releases = filterReleases(sw360User, filter, releaseIds); } else { - restControllerHelper.addEmbeddedFields(field.getValue(), sw360Project.getFieldValue(field.getKey()), - halProject); + releases = releaseIds.stream().map(relId -> wrapTException(() -> { + final Release sw360Release = releaseService.getReleaseForUserById(relId, sw360User); + releaseService.setComponentDependentFieldsInRelease(sw360Release, sw360User); + return sw360Release; + })).collect(Collectors.toList()); + } + List> releaseList = releases.stream().map(sw360Release -> wrapTException(() -> { + final Release embeddedRelease = restControllerHelper.convertToEmbeddedReleaseAttachments(sw360Release); + final HalResource releaseResource = restControllerHelper.addEmbeddedReleaseLinks(embeddedRelease); + return releaseResource; + })).collect(Collectors.toList()); + + List attachmentUsages = attachmentService.getAllAttachmentUsage(id); + String prefix = "{\"" + SW360_ATTACHMENT_USAGES + "\":["; + String serializedUsages = attachmentUsages.stream() + .map(usage -> wrapTException(() -> THRIFT_JSON_SERIALIZER.toString(usage))) + .collect(Collectors.joining(",", prefix, "]}")); + GsonJsonParser parser = new GsonJsonParser(); + Map attachmentUsageMap = parser.parseMap(serializedUsages); + List> listOfAttachmentUsages = (List>) attachmentUsageMap + .get(SW360_ATTACHMENT_USAGES); + listOfAttachmentUsages.removeIf(Objects::isNull); + for (Map attachmentUsage : listOfAttachmentUsages) { + attachmentUsage.remove("revision"); + attachmentUsage.remove("type"); + Object usageData=attachmentUsage.get("usageData"); + if (usageData != null) { + Map licenseInfo = ((Map>) usageData).get("licenseInfo"); + if (licenseInfo != null) { + Object includeConcludedLicense = licenseInfo.get("includeConcludedLicense"); + if (includeConcludedLicense != null) { + if ((Double)includeConcludedLicense == 0.0) { + licenseInfo.put("includeConcludedLicense", false); + } else { + licenseInfo.put("includeConcludedLicense", true); + } + } + } + } } + + List> releaseObjMap = getReleaseObjectMapper(releaseList); + HalResource userHalResource = attachmentUsageReleases(sw360Project, releaseObjMap, listOfAttachmentUsages); + return new ResponseEntity<>(userHalResource, HttpStatus.OK); } - return halProject; - } - private Project convertToProject(Map requestBody) { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.registerModule(sw360Module); + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + description = "Import SBOM in SPDX format.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/import/SBOM", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + public ResponseEntity importSBOM( + @Parameter(description = "Type of SBOM", example = "SPDX") + @RequestParam(value = "type", required = true) String type, + @Parameter(description = "SBOM file") + @RequestBody MultipartFile file + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Attachment attachment = null; + final RequestSummary requestSummary; + String projectId = null; + Map messageMap = new HashMap<>(); + + if (!(type.equalsIgnoreCase("SPDX") || type.equalsIgnoreCase("CycloneDX"))) { + return new ResponseEntity("Invalid SBOM file type. Only SPDX(.rdf/.xml) and CycloneDX(.json/.xml) files are supported.", + HttpStatus.BAD_REQUEST); + } - if (requestBody.containsKey(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY))) { try { - String visibility = (String) requestBody - .get(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY)); - requestBody.put(mapOfProjectFieldsToRequestBody.get(Project._Fields.VISBILITY), - visibility.toUpperCase()); - } catch (IllegalArgumentException e) { - System.err.println("Error processing visibility field: " + e.getMessage()); - System.err.println("Failed requestBody: " + requestBody); - throw e; + attachment = attachmentService.uploadAttachment(file, new Attachment(), sw360User); + } catch (IOException e) { + log.error("failed to upload attachment", e); + throw new RuntimeException("failed to upload attachment", e); } - } - if (requestBody.containsKey("linkedProjects")) { - Map linkedProjects = (Map) requestBody.get("linkedProjects"); - linkedProjects.entrySet().stream().forEach(entry -> { - if (entry.getValue() instanceof String) { - Map projectRelationshipMap = new HashMap<>(); - projectRelationshipMap.put("projectRelationship", entry.getValue()); - linkedProjects.put(entry.getKey(), projectRelationshipMap); - } - }); - } - return mapper.convertValue(requestBody, Project.class); - } + if (type.equalsIgnoreCase("SPDX")) { + requestSummary = projectService.importSPDX(sw360User, attachment.getAttachmentContentId()); - public static TSerializer getJsonSerializer() { - try { - return new TSerializer(new TSimpleJSONProtocol.Factory()); - } catch (TTransportException e) { - log.error("Error creating TSerializer " + e); - } - return null; - } + if (!(requestSummary.getRequestStatus() == RequestStatus.SUCCESS)) { + return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); + } + projectId = requestSummary.getMessage(); + } else { + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), ""); - @Operation( - description = "Get project count of a user.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/projectcount", method = RequestMethod.GET) - public void getUserProjectCount(HttpServletResponse response) throws TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - try { - JsonObject resultJson = new JsonObject(); - resultJson.addProperty("status", "success"); - resultJson.addProperty("count", projectService.getMyAccessibleProjectCounts(sw360User)); - response.getWriter().write(resultJson.toString()); - } catch (IOException e) { - throw new SW360Exception(e.getMessage()); - } - } + if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { + return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); + } + else if(requestSummary.getRequestStatus() == RequestStatus.ACCESS_DENIED){ + return new ResponseEntity("You do not have sufficient permissions.", HttpStatus.UNAUTHORIZED); + } - @Operation( - description = "Get license clearing info for a project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenseClearingCount", method = RequestMethod.GET) - public void getlicenseClearingCount(HttpServletResponse response , - @Parameter(description = "Project ID", example = "376521") - @PathVariable("id") String id) throws TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); + String jsonMessage = requestSummary.getMessage(); + messageMap = new Gson().fromJson(jsonMessage, Map.class); + projectId = messageMap.get("projectId"); - Project proj = projectService.getClearingInfo(sw360Project, sw360User); - ReleaseClearingStateSummary clearingInfo = proj.getReleaseClearingStateSummary(); - int releaseCount = clearingInfo.newRelease + clearingInfo.sentToClearingTool + clearingInfo.underClearing + clearingInfo.reportAvailable + clearingInfo.scanAvailable + clearingInfo.approved; - int approvedCount = clearingInfo.approved; - try { - JsonObject row = new JsonObject(); - row.addProperty("Release Count", releaseCount); - row.addProperty("Approved Count", approvedCount); - response.getWriter().write(row.toString()); - } catch (IOException e) { - throw new SW360Exception(e.getMessage()); - } - } + if (requestSummary.getRequestStatus() == RequestStatus.DUPLICATE) { + return new ResponseEntity("A project with same name and version already exists. The projectId is: " + + projectId, HttpStatus.CONFLICT); + } + } + Project project = projectService.getProjectForUserById(projectId, sw360User); + HalResource halResource = createHalProject(project, sw360User); + return new ResponseEntity>(halResource, HttpStatus.OK); + } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Import SBOM on a project.", + description = "Import a SBOM on a project. Currently only CycloneDX(.xml/" + + ".json) files are supported.", + responses = { + @ApiResponse( + responseCode = "200", description = "Project successfully imported.", + content = { + @Content(mediaType = "application/json", + schema = @Schema(implementation = Project.class)) + } + ), + @ApiResponse( + responseCode = "409", description = "A project with same name and version already exists.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "A project with same name and version already exists. " + + "The projectId is: 376576" + )) + } + ), + }, + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/import/SBOM", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + public ResponseEntity importSBOMonProject( + @Parameter(description = "Project ID", example = "376576") + @PathVariable(value = "id", required = true) String id, + @Parameter(description = "SBOM file") + @RequestBody MultipartFile file + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Attachment attachment = null; + final RequestSummary requestSummary; + String projectId = null; + Map messageMap = new HashMap<>(); - @Operation( - description = "Get license obligations data from license database.", - tags = {"Project"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenseDbObligations", method = RequestMethod.GET) - public ResponseEntity getLicObligations(Pageable pageable, - @Parameter(description = "Project ID.") @PathVariable("id") String id) - throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - if (CommonUtils.isNullOrEmptyMap(sw360Project.getReleaseIdToUsage())) { - return new ResponseEntity("No release linked to the project", HttpStatus.NO_CONTENT); - } - Map licenseInfoAttachmentUsage = projectService.getLicenseInfoAttachmentUsage(id); - if(licenseInfoAttachmentUsage.size() == 0) { - return new ResponseEntity("No approved CLI or licenseInfo attachment usage present for the project", HttpStatus.NO_CONTENT); - } - Map> licensesFromAttachmentUsage = projectService.getLicensesFromAttachmentUsage(licenseInfoAttachmentUsage, sw360User); - Map licenseObligation = projectService.getLicenseObligationData(licensesFromAttachmentUsage, sw360User); + try { + attachment = attachmentService.uploadAttachment(file, new Attachment(), sw360User); + } catch (IOException e) { + log.error("failed to upload attachment", e); + throw new RuntimeException("failed to upload attachment", e); + } - Map responseBody = createPaginationMetadata(pageable, licenseObligation); - return new ResponseEntity<>(responseBody, HttpStatus.OK); - } + requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id); - private Map createPaginationMetadata(Pageable pageable, Map licenseObligation) { - List> entries = new ArrayList<>(licenseObligation.entrySet()); - boolean isDefaultPaged = pageable != null && pageable.getPageSize() == 20 && pageable.getPageNumber() == 0; - boolean isPaged = pageable != null && pageable.isPaged() && !isDefaultPaged; - int pageSize = isPaged ? pageable.getPageSize() : entries.size(); - int pageNumber = isPaged ? pageable.getPageNumber() : 0; - int start = pageNumber * pageSize; - int end = Math.min(start + pageSize, entries.size()); - - List> paginatedEntries = entries.subList(start, end); - int totalPages = (int) Math.ceil((double) licenseObligation.size()/ pageSize); - - Map paginatedMap = new LinkedHashMap<>(); - for (Map.Entry entry : paginatedEntries) { - paginatedMap.put(entry.getKey(), entry.getValue()); - } - Map pagination = Map.of( - "size", pageSize, - "totalElements", licenseObligation.size(), - "totalPages", isPaged ? totalPages : 1, - "number", pageNumber - ); - Map responseBody = new HashMap<>(); - responseBody.put("page", pagination); - responseBody.put("obligations", paginatedMap); - return responseBody; - } + if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) { + return new ResponseEntity(requestSummary.getMessage(), HttpStatus.BAD_REQUEST); + }else if(requestSummary.getRequestStatus() == RequestStatus.ACCESS_DENIED){ + return new ResponseEntity("You do not have sufficient permissions.", HttpStatus.UNAUTHORIZED); + } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "delete orphan obligations", - description = "Pass an array of orphan obligation titles in request body.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/orphanObligation", method = RequestMethod.PATCH) - public ResponseEntity removeOrphanObligation( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Array of orphaned obligations title", - examples = { - @ExampleObject(value = "[\"title1\",\"title2\",\"title3\"]") - // TODO: Add example for MAP value - } - ) - @RequestBody List obligationTitlesInRequestBody - ) throws URISyntaxException, TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + String jsonMessage = requestSummary.getMessage(); + messageMap = new Gson().fromJson(jsonMessage, Map.class); + projectId = messageMap.get("projectId"); - ObligationList obligation = new ObligationList(); - RequestStatus status = null; - Map obligationStatusMap = Maps.newHashMap(); + if (requestSummary.getRequestStatus() == RequestStatus.DUPLICATE) { + return new ResponseEntity( + "A project with same name and version already exists. The projectId is: " + projectId, + HttpStatus.CONFLICT); + }else if (requestSummary.getRequestStatus() == RequestStatus.FAILED_SANITY_CHECK){ + return new ResponseEntity( + "Project name or version present in SBOM metadata tag is not same as the current SW360 project!", + HttpStatus.BAD_REQUEST); + } - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { - obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); - obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); - status = projectService.removeOrphanObligations(obligationStatusMap, obligationTitlesInRequestBody, sw360Project, sw360User, obligation); - } else { - return new ResponseEntity<>("No linked obligation found for the project", HttpStatus.NOT_FOUND); - } - if (status == RequestStatus.SUCCESS) { - return new ResponseEntity<>("Orphaned Obligation Removed Successfully", HttpStatus.OK); - } - return new ResponseEntity<>("Failed to Remove Orphaned Obligation", HttpStatus.NOT_FOUND); - } + Project project = projectService.getProjectForUserById(projectId, sw360User); + HalResource halResource = createHalProject(project, sw360User); + return new ResponseEntity>(halResource, HttpStatus.OK); + } + + @Operation( + summary = "Get a single project with dependencies network.", + responses = { + @ApiResponse( + responseCode = "200", description = "Project successfully imported.", + content = { + @Content(mediaType = "application/json", + schema = @Schema(implementation = ProjectDTO.class)) + } + ), + @ApiResponse( + responseCode = "500", description = "Project release relationship is not enabled.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "Please enable flexible project release relationship " + + "configuration to use this function (enable.flexible.project.release.relationship = true)" + )) + } + ), + }, + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/network/{id}", method = RequestMethod.GET) + public ResponseEntity getProjectWithNetwork( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String id + ) throws TException { + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + } - @Operation( - description = "Get license obligation data of project tab.", - tags = {"Project"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenseObligations", method = RequestMethod.GET) - public ResponseEntity getLicenseObligations(Pageable pageable, - @Parameter(description = "Project ID.") @PathVariable("id") String id) - throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - final Map releaseIdToAcceptedCLI = Maps.newHashMap(); - List releases = new ArrayList<>();; - ObligationList obligation = new ObligationList(); - Map obligationStatusMap = Maps.newHashMap(); - List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); - for (final String releaseId : releaseIds) { - Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); - if (sw360Release.getAttachmentsSize() > 0) { - releases.add(sw360Release); + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); + HalResource projectDTOHalResource = createHalProjectDTO(sw360Project, sw360User); + return new ResponseEntity<>(projectDTOHalResource, HttpStatus.OK); + } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Create a project with dependencies network.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL+ "/network", method = RequestMethod.POST) + public ResponseEntity createProjectWithNetwork( + @Parameter(description = "Project with `dependencyNetwork` set.", + schema = @Schema(implementation = Project.class)) + @RequestBody Map reqBodyMap + ) throws TException { + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + } + + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = convertToProject(reqBodyMap); + project.unsetReleaseIdToUsage(); + try { + addOrPatchDependencyNetworkToProject(project, reqBodyMap, ProjectOperation.CREATE); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + return ResponseEntity.badRequest().body("Invalid dependency network format"); + } catch (SW360Exception sw360Exception) { + log.error(sw360Exception.getWhy()); + return ResponseEntity.badRequest().body(sw360Exception.getWhy()); } - } - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { - obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); - obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); - releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(obligationStatusMap)); - } - obligationStatusMap = projectService.setLicenseInfoWithObligations(obligationStatusMap, releaseIdToAcceptedCLI, releases, sw360User); - for (Map.Entry entry : obligationStatusMap.entrySet()) { - ObligationStatusInfo statusInfo = entry.getValue(); - Set limitedSet = releaseService.getReleasesForUserByIds(statusInfo.getReleaseIdToAcceptedCLI().keySet()); - statusInfo.setReleases(limitedSet); + projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(project, sw360User); + + project = projectService.createProject(project, sw360User); + URI location = ServletUriComponentsBuilder + .fromCurrentRequest().path("/{id}") + .buildAndExpand(project.getId()).toUri(); + + + HalResource halResource = createHalProjectDTO(project, sw360User); + return ResponseEntity.created(location).body(halResource); } - Map responseBody = createPaginationMetadata(pageable, obligationStatusMap); - HalResource> halObligation = new HalResource<>(responseBody); - return new ResponseEntity<>(halObligation, HttpStatus.OK); - } + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Update a project with dependencies network.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/network/{id}", method = RequestMethod.PATCH) + public ResponseEntity patchProjectWithNetwork( + @Parameter(description = "Project ID", example = "376576") @PathVariable("id") String id, + @Parameter( + description = "Project with `dependencyNetwork` set.", + schema = @Schema(implementation = Project.class) + ) @RequestBody Map reqBodyMap) throws TException { - @Operation( - description = "Get obligation data of project tab.", - tags = {"Project"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/obligation", method = RequestMethod.GET) - public ResponseEntity getObligations(Pageable pageable, - @Parameter(description = "Project ID.") @PathVariable("id") String id, - @Parameter(description = "Obligation Level", - schema = @Schema(allowableValues = {"license", "project", "organization", "component"})) - @RequestParam(value = "obligationLevel", required = true) String oblLevel) - throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - final Map releaseIdToAcceptedCLI = Maps.newHashMap(); - List releases = new ArrayList<>();; - ObligationList obligation = new ObligationList(); - Map obligationStatusMap = Maps.newHashMap(); - Map oblData = Maps.newHashMap(); - Map filterData = Maps.newHashMap(); + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + } - List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); - for (final String releaseId : releaseIds) { - Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); - if (sw360Release.getAttachmentsSize() > 0) { - releases.add(sw360Release); + User user = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, user); + Project updateProject = convertToProject(reqBodyMap); + updateProject.unsetReleaseIdToUsage(); + sw360Project.unsetReleaseIdToUsage(); + + try { + addOrPatchDependencyNetworkToProject(updateProject, reqBodyMap, ProjectOperation.UPDATE); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + return ResponseEntity.badRequest().body("Invalid dependency network format"); + } catch (SW360Exception sw360Exception) { + log.error(sw360Exception.getWhy()); + return ResponseEntity.badRequest().body(sw360Exception.getWhy()); + } + String comment = (String) reqBodyMap.get("comment"); + if (!restControllerHelper.isWriteActionAllowed(updateProject, user) && comment == null) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST_WITH_COMMIT, HttpStatus.ACCEPTED); + } + user.setCommentMadeDuringModerationRequest(comment); + sw360Project = this.restControllerHelper.updateProject(sw360Project, updateProject, reqBodyMap, mapOfProjectFieldsToRequestBody); + projectService.syncReleaseRelationNetworkAndReleaseIdToUsage(sw360Project, user); + + RequestStatus updateProjectStatus = projectService.updateProject(sw360Project, user); + if (updateProjectStatus == RequestStatus.SENT_TO_MODERATOR) { + return new ResponseEntity<>(RESPONSE_BODY_FOR_MODERATION_REQUEST, HttpStatus.ACCEPTED); } + HalResource projectDTOHalResource = createHalProjectDTO(sw360Project, user); + return new ResponseEntity<>(projectDTOHalResource, HttpStatus.OK); } - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { - obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); - obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); - oblData = projectService.setObligationsFromAdminSection(sw360User, obligationStatusMap, sw360Project, oblLevel); - } else { - oblData = projectService.setObligationsFromAdminSection(sw360User, new HashMap(), sw360Project, oblLevel); + + + @Override + public RepositoryLinksResource process(RepositoryLinksResource resource) { + resource.add(linkTo(ProjectController.class).slash("api" + PROJECTS_URL).withRel("projects")); + return resource; } - if (oblLevel.equalsIgnoreCase("License")) { - for (Map.Entry entry : obligationStatusMap.entrySet()) { - String key = entry.getKey(); - if (!key.equals("compObl") && !key.equals("projectObl") && !key.equals("orgObl")) { - filterData.put(key, entry.getValue()); - } - } - releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(filterData)); - oblData = projectService.setLicenseInfoWithObligations(filterData, releaseIdToAcceptedCLI, releases, sw360User); - for (Map.Entry entry : oblData.entrySet()) { - ObligationStatusInfo statusInfo = entry.getValue(); - Set limitedSet = releaseService.getReleasesForUserByIds(statusInfo.getReleaseIdToAcceptedCLI().keySet()); - statusInfo.setReleases(limitedSet); + private HalResource createHalLicenseClearing(Project sw360Project, List> releases) { + Project sw360 = new Project(); + Map releaseIdToUsage = sw360Project.getReleaseIdToUsage(); + sw360.setReleaseIdToUsage(sw360Project.getReleaseIdToUsage()); + sw360.setLinkedProjects(sw360Project.getLinkedProjects()); + sw360.unsetState(); + sw360.unsetProjectType(); + sw360.unsetVisbility(); + sw360.unsetSecurityResponsibles(); + HalResource halProject = new HalResource<>(sw360); + + if (releaseIdToUsage != null) { + restControllerHelper.addEmbeddedProjectReleases(halProject, releases); } + return halProject; } - Map responseBody = createPaginationMetadata(pageable, oblData); - HalResource> halObligation = new HalResource<>(responseBody); - return new ResponseEntity<>(halObligation, HttpStatus.OK); - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Add licenseObligations from license DB", - description = "Pass an array of obligation ids in request body.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/licenseObligation", method = RequestMethod.POST) - public ResponseEntity addLicenseObligations( - @Parameter(description = "License Obligation ID.") - @PathVariable("id") String id, - @Parameter(description = "Set of license obligation IDs to be added.", - example = "[\"3765276512\",\"5578999\",\"3765276513\"]" - ) - @RequestBody List obligationIds - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - Map licenseInfoAttachmentUsage = projectService.getLicenseInfoAttachmentUsage(id); - Map> licensesFromAttachmentUsage = projectService.getLicensesFromAttachmentUsage( - licenseInfoAttachmentUsage, sw360User); - Map licenseObligation = projectService.getLicenseObligationData(licensesFromAttachmentUsage, sw360User); - Map selectedLicenseObligation = new HashMap(); + private RequestStatus addOrPatchReleasesToProject(String id, Object releasesInRequestBody, boolean patch) + throws URISyntaxException, TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project project = projectService.getProjectForUserById(id, sw360User); + Map releaseIdToUsage = new HashMap<>(); + if (patch) { + releaseIdToUsage = project.getReleaseIdToUsage(); + } - ObligationList obligation = new ObligationList(); - Map obligationStatusMap = Maps.newHashMap(); - Map obligationStatusMapFromReport = Maps.newHashMap(); - final Map releaseIdToAcceptedCLI = Maps.newHashMap(); - List releases = new ArrayList<>(); - List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); - for (final String releaseId : releaseIds) { - Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); - if (sw360Release.getAttachmentsSize() > 0) { - releases.add(sw360Release); - } - } - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { - obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); - obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); - releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(obligationStatusMap)); - } else { - obligationStatusMapFromReport = projectService.setLicenseInfoWithObligations(obligationStatusMap, releaseIdToAcceptedCLI, releases, sw360User); - } + if (releasesInRequestBody instanceof List) { + List releasesAsList = (List) releasesInRequestBody; + for (String release : releasesAsList) { + URI releaseURI = new URI(release.toString()); + String path = releaseURI.getPath(); + String releaseId = path.substring(path.lastIndexOf('/') + 1); + releaseIdToUsage.put(releaseId, + new ProjectReleaseRelationship(ReleaseRelationship.CONTAINED, MainlineState.OPEN)); + } + } else if (releasesInRequestBody instanceof Map) { + Map releaseAsMap = (Map) releasesInRequestBody; + for (Entry entry : releaseAsMap.entrySet()) { + String releaseId = entry.getKey(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.registerModule(sw360Module); + ProjectReleaseRelationship requestBodyProjectReleaseRelationship = mapper.convertValue(entry.getValue(), + ProjectReleaseRelationship.class); + ProjectReleaseRelationship actualProjectReleaseRelationship = null; + + if (patch && releaseIdToUsage.containsKey(releaseId)) { + actualProjectReleaseRelationship = releaseIdToUsage.get(releaseId); + } else { + actualProjectReleaseRelationship = new ProjectReleaseRelationship(ReleaseRelationship.CONTAINED, + MainlineState.OPEN).setCreatedBy(sw360User.getEmail()) + .setCreatedOn(SW360Utils.getCreatedOn()); + } - if (licenseObligation.size() == 0) { - return new ResponseEntity<>("No License Obligations Present", HttpStatus.NO_CONTENT); - } - for (Map.Entry entry : licenseObligation.entrySet()) { - String oblId = entry.getValue().getId(); - if (obligationIds.contains(oblId)) { - selectedLicenseObligation.put(entry.getKey(), entry.getValue()); + restControllerHelper.updateProjectReleaseRelationship(actualProjectReleaseRelationship, + requestBodyProjectReleaseRelationship); + releaseIdToUsage.put(releaseId, actualProjectReleaseRelationship); + } + } else { + throw new HttpMessageNotReadableException( + "Request body should be List of valid release id or map of release id to usage"); } + project.setReleaseIdToUsage(releaseIdToUsage); + return projectService.updateProject(project, sw360User); } - selectedLicenseObligation.putAll(obligationStatusMapFromReport); - RequestStatus requestStatus= projectService.addLinkedObligations(sw360Project, sw360User, selectedLicenseObligation); - if (requestStatus == RequestStatus.SUCCESS) { - return new ResponseEntity<>("License Obligation Added Successfully", HttpStatus.CREATED); - } - return new ResponseEntity<>("Failed to add/update obligation for project", HttpStatus.NOT_FOUND); - } - @PreAuthorize("hasAuthority('WRITE')") - @Operation( - summary = "Update License Obligations ", - description = "Pass a map of obligations in request body.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/updateLicenseObligation", method = RequestMethod.PATCH) - public ResponseEntity patchLicenseObligations( - @Parameter(description = "Project ID.") - @PathVariable("id") String id, - @Parameter(description = "Map of obligation status info.") - @RequestBody Map requestBodyObligationStatusInfo - ) throws TException { - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - final Project sw360Project = projectService.getProjectForUserById(id, sw360User); - ObligationList obligation = new ObligationList(); - Map obligationStatusMap = Maps.newHashMap(); - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { - obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); - obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); - } - Map updatedObligationStatusMap = projectService - .compareObligationStatusMap(sw360User, obligationStatusMap, requestBodyObligationStatusInfo); - RequestStatus updateObligationStatus = projectService.patchLinkedObligations(sw360User, updatedObligationStatusMap, obligation); - if (updateObligationStatus == RequestStatus.SUCCESS) { - return new ResponseEntity<>("License Obligation Updated Successfully", HttpStatus.CREATED); - } - return new ResponseEntity<>("Cannot update License Obligation", HttpStatus.CONFLICT); - } - @Operation( - description = "Get summary and administration page of project tab.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/{id}/summaryAdministration", method = RequestMethod.GET) - public ResponseEntity> getAdministration( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id - ) throws TException { - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - Project sw360Project = projectService.getProjectForUserById(id, sw360User); - Map sortedExternalURLs = CommonUtils.getSortedMap(sw360Project.getExternalUrls(), true); - sw360Project.setExternalUrls(sortedExternalURLs); - sw360Project.setReleaseIdToUsage(null); - sw360Project.setLinkedProjects(null); - sw360Project.setAttachments(null); - sw360Project.setPackageIds(null); - HalResource userHalResource = createHalProject(sw360Project, sw360User); - setAdditionalFieldsToHalResource(sw360Project,userHalResource); - sw360Project.unsetLinkedProjects(); - sw360Project.unsetReleaseIdToUsage(); - sw360Project.unsetProjectResponsible(); - sw360Project.unsetSecurityResponsibles(); - sw360Project.unsetLicenseInfoHeaderText(); + private HalResource createHalProjectResourceWithAllDetails(Project sw360Project, User sw360User) { + HalResource halProject = new HalResource<>(sw360Project); + halProject.addEmbeddedResource(CREATED_BY, sw360Project.getCreatedBy()); - return new ResponseEntity<>(userHalResource, HttpStatus.OK); - } + Set packageIds = sw360Project.getPackageIds(); - @Operation( - summary = "Get a list view of dependency network for a project.", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/network/{id}/listView", method = RequestMethod.GET) - public ResponseEntity getListViewDependencyNetwork( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String projectId - ) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); - } + if (packageIds != null) { + for (String id : sw360Project.getPackageIds()) { + Link packageLink = linkTo(ProjectController.class) + .slash("api" + PackageController.PACKAGES_URL + "/" + id).withRel("packages"); + halProject.add(packageLink); + } + } - User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - List> clearingStatusList = projectService.serveDependencyNetworkListView(projectId, sw360User); - return new ResponseEntity<>(clearingStatusList, HttpStatus.OK); - } + List obsoleteFields = List.of("homepage", "wiki"); + for (Entry field : mapOfFieldsTobeEmbedded.entrySet()) { + if (Project._Fields.EXTERNAL_URLS.equals(field.getKey())) { + Map externalUrls = CommonUtils + .nullToEmptyMap((Map) sw360Project.getFieldValue(field.getKey())); + restControllerHelper.addEmbeddedFields(obsoleteFields.get(0), + externalUrls.get(obsoleteFields.get(0)) == null ? "" : externalUrls.get(obsoleteFields.get(0)), + halProject); + restControllerHelper.addEmbeddedFields(obsoleteFields.get(1), + externalUrls.get(obsoleteFields.get(1)) == null ? "" : externalUrls.get(obsoleteFields.get(1)), + halProject); + } else { + restControllerHelper.addEmbeddedFields(field.getValue(), sw360Project.getFieldValue(field.getKey()), + halProject); + } + } - @Operation( - description = "Get linked resources (projects, releases) of a project", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/network/{id}/linkedResources", method = RequestMethod.GET) - public ResponseEntity getLinkedResourcesOfProjectForDependencyNetwork( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String id, - @Parameter(description = "Get linked releases transitively (default is false)", example = "true") - @RequestParam(value = "transitive", required = false, defaultValue = "false") boolean transitive) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + return halProject; } - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - ProjectLink projectLink = projectService.serveLinkedResourcesOfProjectInDependencyNetwork(id, transitive, sw360User); - return new ResponseEntity<>(projectLink, HttpStatus.OK); - } - @Operation( - description = "Get indirect linked releases of a project in dependency network by release's index path", - tags = {"Projects"} - ) - @RequestMapping(value = PROJECTS_URL + "/network/{id}/releases", method = RequestMethod.GET) - public ResponseEntity getLinkedReleasesInDependencyNetworkByIndexPath( - @Parameter(description = "Project ID", example = "376576") - @PathVariable("id") String projectId, - @Parameter(description = "Index path", example = "0->1") - @RequestParam(value = "path", required = false) String releaseIndexPath - ) throws TException { - if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { - return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + + @Operation( + description = "Get project count of a user.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/projectcount", method = RequestMethod.GET) + public void getUserProjectCount(HttpServletResponse response) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + try { + JsonObject resultJson = new JsonObject(); + resultJson.addProperty("status", "success"); + resultJson.addProperty("count", projectService.getMyAccessibleProjectCounts(sw360User)); + response.getWriter().write(resultJson.toString()); + } catch (IOException e) { + throw new SW360Exception(e.getMessage()); + } } - final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); - if (!CommonUtils.isNotNullEmptyOrWhitespace(releaseIndexPath)) { - ProjectLink projectLink = projectService.serveLinkedResourcesOfProjectInDependencyNetwork(projectId, false, sw360User); - return new ResponseEntity<>(CollectionModel.of(projectLink.getLinkedReleases()), HttpStatus.OK); + @Operation( + description = "Get license clearing info for a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenseClearingCount", method = RequestMethod.GET) + public void getlicenseClearingCount(HttpServletResponse response , + @Parameter(description = "Project ID", example = "376521") + @PathVariable("id") String id) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); + + Project proj = projectService.getClearingInfo(sw360Project, sw360User); + ReleaseClearingStateSummary clearingInfo = proj.getReleaseClearingStateSummary(); + int releaseCount = clearingInfo.newRelease + clearingInfo.sentToClearingTool + clearingInfo.underClearing + clearingInfo.reportAvailable + clearingInfo.scanAvailable + clearingInfo.approved; + int approvedCount = clearingInfo.approved; + try { + JsonObject row = new JsonObject(); + row.addProperty("Release Count", releaseCount); + row.addProperty("Approved Count", approvedCount); + response.getWriter().write(row.toString()); + } catch (IOException e) { + throw new SW360Exception(e.getMessage()); + } } - List indexPath = Arrays.asList(releaseIndexPath.split("->")); - try { - List releaseLinks = projectService.serveLinkedReleasesInDependencyNetworkByIndexPath(projectId, indexPath, sw360User); - CollectionModel resources = CollectionModel.of(releaseLinks); - return new ResponseEntity<>(resources, HttpStatus.OK); - } catch (SW360Exception exception) { - if (exception.getErrorCode() == 404) { - throw new ResourceNotFoundException("Requested project not found: " + projectId); + @Operation( + description = "Get license obligations data from license database.", + tags = {"Project"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenseDbObligations", method = RequestMethod.GET) + public ResponseEntity getLicObligations(Pageable pageable, + @Parameter(description = "Project ID.") @PathVariable("id") String id) + throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + if (CommonUtils.isNullOrEmptyMap(sw360Project.getReleaseIdToUsage())) { + return new ResponseEntity("No release linked to the project", HttpStatus.NO_CONTENT); } - throw new RuntimeException(exception.getWhy()); + Map licenseInfoAttachmentUsage = projectService.getLicenseInfoAttachmentUsage(id); + if(licenseInfoAttachmentUsage.size() == 0) { + return new ResponseEntity("No approved CLI or licenseInfo attachment usage present for the project", HttpStatus.NO_CONTENT); + } + Map> licensesFromAttachmentUsage = projectService.getLicensesFromAttachmentUsage(licenseInfoAttachmentUsage, sw360User); + Map licenseObligation = projectService.getLicenseObligationData(licensesFromAttachmentUsage, sw360User); + + Map responseBody = createPaginationMetadata(pageable, licenseObligation); + return new ResponseEntity<>(responseBody, HttpStatus.OK); } - } - private void setAdditionalFieldsToHalResource(Project sw360Project, HalResource userHalResource) throws TException { - try { - String modifiedByEmail = sw360Project.getModifiedBy(); - if (modifiedByEmail != null) { - User projectModifier = restControllerHelper.getUserByEmail(modifiedByEmail); - if (projectModifier != null) { - restControllerHelper.addEmbeddedUser(userHalResource, projectModifier, "modifiedBy"); + private Map createPaginationMetadata(Pageable pageable, Map licenseObligation) { + List> entries = new ArrayList<>(licenseObligation.entrySet()); + boolean isDefaultPaged = pageable != null && pageable.getPageSize() == 20 && pageable.getPageNumber() == 0; + boolean isPaged = pageable != null && pageable.isPaged() && !isDefaultPaged; + int pageSize = isPaged ? pageable.getPageSize() : entries.size(); + int pageNumber = isPaged ? pageable.getPageNumber() : 0; + int start = pageNumber * pageSize; + int end = Math.min(start + pageSize, entries.size()); + + List> paginatedEntries = entries.subList(start, end); + int totalPages = (int) Math.ceil((double) licenseObligation.size()/ pageSize); + + Map paginatedMap = new LinkedHashMap<>(); + for (Map.Entry entry : paginatedEntries) { + paginatedMap.put(entry.getKey(), entry.getValue()); + } + Map pagination = Map.of( + "size", pageSize, + "totalElements", licenseObligation.size(), + "totalPages", isPaged ? totalPages : 1, + "number", pageNumber + ); + Map responseBody = new HashMap<>(); + responseBody.put("page", pagination); + responseBody.put("obligations", paginatedMap); + return responseBody; + } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "delete orphan obligations", + description = "Pass an array of orphan obligation titles in request body.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/orphanObligation", method = RequestMethod.PATCH) + public ResponseEntity removeOrphanObligation( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Array of orphaned obligations title", + examples = { + @ExampleObject(value = "[\"title1\",\"title2\",\"title3\"]") + // TODO: Add example for MAP value + } + ) + @RequestBody List obligationTitlesInRequestBody + ) throws URISyntaxException, TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + + ObligationList obligation = new ObligationList(); + RequestStatus status = null; + Map obligationStatusMap = Maps.newHashMap(); + + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { + obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); + obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); + status = projectService.removeOrphanObligations(obligationStatusMap, obligationTitlesInRequestBody, sw360Project, sw360User, obligation); + } else { + return new ResponseEntity<>("No linked obligation found for the project", HttpStatus.NOT_FOUND); + } + if (status == RequestStatus.SUCCESS) { + return new ResponseEntity<>("Orphaned Obligation Removed Successfully", HttpStatus.OK); + } + return new ResponseEntity<>("Failed to Remove Orphaned Obligation", HttpStatus.NOT_FOUND); + } + + @Operation( + description = "Get license obligation data of project tab.", + tags = {"Project"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenseObligations", method = RequestMethod.GET) + public ResponseEntity getLicenseObligations(Pageable pageable, + @Parameter(description = "Project ID.") @PathVariable("id") String id) + throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + final Map releaseIdToAcceptedCLI = Maps.newHashMap(); + List releases = new ArrayList<>();; + ObligationList obligation = new ObligationList(); + Map obligationStatusMap = Maps.newHashMap(); + List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); + for (final String releaseId : releaseIds) { + Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); + if (sw360Release.getAttachmentsSize() > 0) { + releases.add(sw360Release); } } - String projectOwnerEmail = sw360Project.getProjectOwner(); - if (projectOwnerEmail != null) { - User projectOwner = restControllerHelper.getUserByEmail(sw360Project.getProjectOwner()); - if (projectOwner != null) { - restControllerHelper.addEmbeddedUser(userHalResource, projectOwner, "projectOwner"); + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { + obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); + obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); + releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(obligationStatusMap)); + } + + obligationStatusMap = projectService.setLicenseInfoWithObligations(obligationStatusMap, releaseIdToAcceptedCLI, releases, sw360User); + for (Map.Entry entry : obligationStatusMap.entrySet()) { + ObligationStatusInfo statusInfo = entry.getValue(); + Set limitedSet = releaseService.getReleasesForUserByIds(statusInfo.getReleaseIdToAcceptedCLI().keySet()); + statusInfo.setReleases(limitedSet); + } + + Map responseBody = createPaginationMetadata(pageable, obligationStatusMap); + HalResource> halObligation = new HalResource<>(responseBody); + return new ResponseEntity<>(halObligation, HttpStatus.OK); + } + + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Add licenseObligations from license DB", + description = "Pass an array of obligation ids in request body.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/licenseObligation", method = RequestMethod.POST) + public ResponseEntity addLicenseObligations( + @Parameter(description = "License Obligation ID.") + @PathVariable("id") String id, + @Parameter(description = "Set of license obligation IDs to be added.", + example = "[\"3765276512\",\"5578999\",\"3765276513\"]" + ) + @RequestBody List obligationIds + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + Map licenseInfoAttachmentUsage = projectService.getLicenseInfoAttachmentUsage(id); + Map> licensesFromAttachmentUsage = projectService.getLicensesFromAttachmentUsage( + licenseInfoAttachmentUsage, sw360User); + Map licenseObligation = projectService.getLicenseObligationData(licensesFromAttachmentUsage, sw360User); + Map selectedLicenseObligation = new HashMap(); + + ObligationList obligation = new ObligationList(); + Map obligationStatusMap = Maps.newHashMap(); + Map obligationStatusMapFromReport = Maps.newHashMap(); + final Map releaseIdToAcceptedCLI = Maps.newHashMap(); + List releases = new ArrayList<>(); + List releaseIds = new ArrayList<>(sw360Project.getReleaseIdToUsage().keySet()); + for (final String releaseId : releaseIds) { + Release sw360Release = releaseService.getReleaseForUserById(releaseId, sw360User); + if (sw360Release.getAttachmentsSize() > 0) { + releases.add(sw360Release); } } - if (sw360Project.getSecurityResponsibles() == null || sw360Project.getSecurityResponsibles().isEmpty()) { - sw360Project.setSecurityResponsibles(new HashSet(){{add("");}}); + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { + obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); + obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); + releaseIdToAcceptedCLI.putAll(SW360Utils.getReleaseIdtoAcceptedCLIMappings(obligationStatusMap)); + } else { + obligationStatusMapFromReport = projectService.setLicenseInfoWithObligations(obligationStatusMap, releaseIdToAcceptedCLI, releases, sw360User); } - Set securityResponsibles = sw360Project.getSecurityResponsibles(); - restControllerHelper.addEmbeddedSecurityResponsibles(userHalResource, securityResponsibles); - String clearingTeam = sw360Project.getClearingTeam(); - if (clearingTeam != null) { - restControllerHelper.addEmbeddedClearingTeam(userHalResource, clearingTeam, "clearingTeam"); + if (licenseObligation.size() == 0) { + return new ResponseEntity<>("No License Obligations Present", HttpStatus.NO_CONTENT); + } + for (Map.Entry entry : licenseObligation.entrySet()) { + String oblId = entry.getValue().getId(); + if (obligationIds.contains(oblId)) { + selectedLicenseObligation.put(entry.getKey(), entry.getValue()); + } + } + selectedLicenseObligation.putAll(obligationStatusMapFromReport); + RequestStatus requestStatus= projectService.addLinkedObligations(sw360Project, sw360User, selectedLicenseObligation); + if (requestStatus == RequestStatus.SUCCESS) { + return new ResponseEntity<>("License Obligation Added Successfully", HttpStatus.CREATED); } - if (sw360Project.getProjectResponsible() != null) { - restControllerHelper.addEmbeddedProjectResponsible(userHalResource,sw360Project.getProjectResponsible()); + return new ResponseEntity<>("Failed to add/update obligation for project", HttpStatus.NOT_FOUND); + } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Update License Obligations ", + description = "Pass a map of obligations in request body.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/updateLicenseObligation", method = RequestMethod.PATCH) + public ResponseEntity patchLicenseObligations( + @Parameter(description = "Project ID.") + @PathVariable("id") String id, + @Parameter(description = "Map of obligation status info.") + @RequestBody Map requestBodyObligationStatusInfo + ) throws TException { + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + final Project sw360Project = projectService.getProjectForUserById(id, sw360User); + ObligationList obligation = new ObligationList(); + Map obligationStatusMap = Maps.newHashMap(); + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getLinkedObligationId())) { + obligation = projectService.getObligationData(sw360Project.getLinkedObligationId(), sw360User); + obligationStatusMap = CommonUtils.nullToEmptyMap(obligation.getLinkedObligationStatus()); + } + Map updatedObligationStatusMap = projectService + .compareObligationStatusMap(sw360User, obligationStatusMap, requestBodyObligationStatusInfo); + RequestStatus updateObligationStatus = projectService.patchLinkedObligations(sw360User, updatedObligationStatusMap, obligation); + if (updateObligationStatus == RequestStatus.SUCCESS) { + return new ResponseEntity<>("License Obligation Updated Successfully", HttpStatus.CREATED); } - } catch (Exception e) { - throw new TException(e.getMessage()); + return new ResponseEntity<>("Cannot update License Obligation", HttpStatus.CONFLICT); } - } - private HalResource createHalProjectDTO(Project sw360Project, User sw360User) throws TException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - ProjectDTO projectDTO = objectMapper.convertValue(sw360Project,ProjectDTO.class); + @Operation( + description = "Get summary and administration page of project tab.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/{id}/summaryAdministration", method = RequestMethod.GET) + public ResponseEntity> getAdministration( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String id + ) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + Project sw360Project = projectService.getProjectForUserById(id, sw360User); + Map sortedExternalURLs = CommonUtils.getSortedMap(sw360Project.getExternalUrls(), true); + sw360Project.setExternalUrls(sortedExternalURLs); + sw360Project.setReleaseIdToUsage(null); + sw360Project.setLinkedProjects(null); + sw360Project.setAttachments(null); + sw360Project.setPackageIds(null); + HalResource userHalResource = createHalProject(sw360Project, sw360User); + setAdditionalFieldsToHalResource(sw360Project,userHalResource); + sw360Project.unsetLinkedProjects(); + sw360Project.unsetReleaseIdToUsage(); + sw360Project.unsetProjectResponsible(); + sw360Project.unsetSecurityResponsibles(); + sw360Project.unsetLicenseInfoHeaderText(); + + return new ResponseEntity<>(userHalResource, HttpStatus.OK); + } + + @Operation( + summary = "Get a list view of dependency network for a project.", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/network/{id}/listView", method = RequestMethod.GET) + public ResponseEntity getListViewDependencyNetwork( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String projectId + ) throws TException { + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + } - List dependencyNetwork = new ArrayList<>(); - if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getReleaseRelationNetwork())) { - try { - dependencyNetwork = objectMapper.readValue(sw360Project.getReleaseRelationNetwork(), new TypeReference<>() { - }); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + List> clearingStatusList = projectService.serveDependencyNetworkListView(projectId, sw360User); + return new ResponseEntity<>(clearingStatusList, HttpStatus.OK); + } + + @Operation( + description = "Get linked resources (projects, releases) of a project", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/network/{id}/linkedResources", method = RequestMethod.GET) + public ResponseEntity getLinkedResourcesOfProjectForDependencyNetwork( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String id, + @Parameter(description = "Get linked releases transitively (default is false)", example = "true") + @RequestParam(value = "transitive", required = false, defaultValue = "false") boolean transitive) throws TException { + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + ProjectLink projectLink = projectService.serveLinkedResourcesOfProjectInDependencyNetwork(id, transitive, sw360User); + return new ResponseEntity<>(projectLink, HttpStatus.OK); } - projectDTO.setDependencyNetwork(dependencyNetwork); - HalResource halProject = new HalResource<>(projectDTO); - User projectCreator = restControllerHelper.getUserByEmail(projectDTO.getCreatedBy()); - restControllerHelper.addEmbeddedUser(halProject, projectCreator, CREATED_BY); + @Operation( + description = "Get indirect linked releases of a project in dependency network by release's index path", + tags = {"Projects"} + ) + @RequestMapping(value = PROJECTS_URL + "/network/{id}/releases", method = RequestMethod.GET) + public ResponseEntity getLinkedReleasesInDependencyNetworkByIndexPath( + @Parameter(description = "Project ID", example = "376576") + @PathVariable("id") String projectId, + @Parameter(description = "Index path", example = "0->1") + @RequestParam(value = "path", required = false) String releaseIndexPath + ) throws TException { + if (!SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP) { + return new ResponseEntity<>(SW360Constants.PLEASE_ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, HttpStatus.INTERNAL_SERVER_ERROR); + } - Map linkedProjects = projectDTO.getLinkedProjects(); - if (linkedProjects != null) { - restControllerHelper.addEmbeddedProjectDTO(halProject, linkedProjects.keySet(), projectService, sw360User); - } + final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + if (!CommonUtils.isNotNullEmptyOrWhitespace(releaseIndexPath)) { + ProjectLink projectLink = projectService.serveLinkedResourcesOfProjectInDependencyNetwork(projectId, false, sw360User); + return new ResponseEntity<>(CollectionModel.of(projectLink.getLinkedReleases()), HttpStatus.OK); + } - if (projectDTO.getModerators() != null) { - Set moderators = projectDTO.getModerators(); - restControllerHelper.addEmbeddedModerators(halProject, moderators); + List indexPath = Arrays.asList(releaseIndexPath.split("->")); + try { + List releaseLinks = projectService.serveLinkedReleasesInDependencyNetworkByIndexPath(projectId, indexPath, sw360User); + CollectionModel resources = CollectionModel.of(releaseLinks); + return new ResponseEntity<>(resources, HttpStatus.OK); + } catch (SW360Exception exception) { + if (exception.getErrorCode() == 404) { + throw new ResourceNotFoundException("Requested project not found: " + projectId); + } + throw new RuntimeException(exception.getWhy()); + } } - if (projectDTO.getAttachments() != null) { - restControllerHelper.addEmbeddedAttachments(halProject, projectDTO.getAttachments()); - } + private void setAdditionalFieldsToHalResource(Project sw360Project, HalResource userHalResource) throws TException { + try { + String modifiedByEmail = sw360Project.getModifiedBy(); + if (modifiedByEmail != null) { + User projectModifier = restControllerHelper.getUserByEmail(modifiedByEmail); + if (projectModifier != null) { + restControllerHelper.addEmbeddedUser(userHalResource, projectModifier, "modifiedBy"); + } + } + String projectOwnerEmail = sw360Project.getProjectOwner(); + if (projectOwnerEmail != null) { + User projectOwner = restControllerHelper.getUserByEmail(sw360Project.getProjectOwner()); + if (projectOwner != null) { + restControllerHelper.addEmbeddedUser(userHalResource, projectOwner, "projectOwner"); + } + } + if (sw360Project.getSecurityResponsibles() == null || sw360Project.getSecurityResponsibles().isEmpty()) { + sw360Project.setSecurityResponsibles(new HashSet(){{add("");}}); + } + Set securityResponsibles = sw360Project.getSecurityResponsibles(); + restControllerHelper.addEmbeddedSecurityResponsibles(userHalResource, securityResponsibles); - if(projectDTO.getLeadArchitect() != null) { - restControllerHelper.addEmbeddedLeadArchitect(halProject, projectDTO.getLeadArchitect()); + String clearingTeam = sw360Project.getClearingTeam(); + if (clearingTeam != null) { + restControllerHelper.addEmbeddedClearingTeam(userHalResource, clearingTeam, "clearingTeam"); + } + if (sw360Project.getProjectResponsible() != null) { + restControllerHelper.addEmbeddedProjectResponsible(userHalResource,sw360Project.getProjectResponsible()); + } + } catch (Exception e) { + throw new TException(e.getMessage()); + } } - if (projectDTO.getContributors() != null) { - Set contributors = projectDTO.getContributors(); - restControllerHelper.addEmbeddedContributors(halProject, contributors); - } + private HalResource createHalProjectDTO(Project sw360Project, User sw360User) throws TException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + ProjectDTO projectDTO = objectMapper.convertValue(sw360Project,ProjectDTO.class); - if (projectDTO.getVendor() != null) { - Vendor vendor = sw360Project.getVendor(); - HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor.getFullname()); - halProject.addEmbeddedResource("sw360:vendors", vendorHalResource); - projectDTO.setVendor(null); - } + List dependencyNetwork = new ArrayList<>(); + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Project.getReleaseRelationNetwork())) { + try { + dependencyNetwork = objectMapper.readValue(sw360Project.getReleaseRelationNetwork(), new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + } + } + projectDTO.setDependencyNetwork(dependencyNetwork); + HalResource halProject = new HalResource<>(projectDTO); - return halProject; - } + User projectCreator = restControllerHelper.getUserByEmail(projectDTO.getCreatedBy()); + restControllerHelper.addEmbeddedUser(halProject, projectCreator, CREATED_BY); - private void addOrPatchDependencyNetworkToProject(Project project, Map requestBody, ProjectOperation operation) - throws JsonProcessingException, SW360Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - List releaseNodes = null; - String dependencyNetwork = objectMapper.writeValueAsString(requestBody.get("dependencyNetwork")); + Map linkedProjects = projectDTO.getLinkedProjects(); + if (linkedProjects != null) { + restControllerHelper.addEmbeddedProjectDTO(halProject, linkedProjects.keySet(), projectService, sw360User); + } - List uniqueDependencyNetwork = new ArrayList<>(); + if (projectDTO.getModerators() != null) { + Set moderators = projectDTO.getModerators(); + restControllerHelper.addEmbeddedModerators(halProject, moderators); + } - if (dependencyNetwork != null && !dependencyNetwork.equals("null")) { - releaseNodes = objectMapper.readValue(dependencyNetwork, new TypeReference<>() { - }); + if (projectDTO.getAttachments() != null) { + restControllerHelper.addEmbeddedAttachments(halProject, projectDTO.getAttachments()); + } - if (releaseNodes != null) { - List releaseWithSameLevel = new ArrayList<>(); - for (ReleaseNode releaseNode : releaseNodes) { - if (CommonUtils.isNullEmptyOrWhitespace(releaseNode.getReleaseId())) { - throw new SW360Exception("releaseId cannot be null or empty"); - } + if(projectDTO.getLeadArchitect() != null) { + restControllerHelper.addEmbeddedLeadArchitect(halProject, projectDTO.getLeadArchitect()); + } - if (releaseWithSameLevel.contains(releaseNode.getReleaseId())) { - continue; - } - releaseWithSameLevel.add(releaseNode.getReleaseId()); - updateReleaseNodeData(releaseNode, operation); - if (releaseNode.getReleaseLink() == null) { - releaseNode.setReleaseLink(Collections.emptyList()); - } - else { - List loadedReleases = new ArrayList<>(); - loadedReleases.add(releaseNode.getReleaseId()); - releaseNode.setReleaseLink(checkAndUpdateSubNodes(releaseNode.getReleaseLink(), operation, loadedReleases)); + if (projectDTO.getContributors() != null) { + Set contributors = projectDTO.getContributors(); + restControllerHelper.addEmbeddedContributors(halProject, contributors); + } + + if (projectDTO.getVendor() != null) { + Vendor vendor = sw360Project.getVendor(); + HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor.getFullname()); + halProject.addEmbeddedResource("sw360:vendors", vendorHalResource); + projectDTO.setVendor(null); + } + + return halProject; + } + + private void addOrPatchDependencyNetworkToProject(Project project, Map requestBody, ProjectOperation operation) + throws JsonProcessingException, SW360Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + List releaseNodes = null; + String dependencyNetwork = objectMapper.writeValueAsString(requestBody.get("dependencyNetwork")); + + List uniqueDependencyNetwork = new ArrayList<>(); + + if (dependencyNetwork != null && !dependencyNetwork.equals("null")) { + releaseNodes = objectMapper.readValue(dependencyNetwork, new TypeReference<>() { + }); + + if (releaseNodes != null) { + List releaseWithSameLevel = new ArrayList<>(); + for (ReleaseNode releaseNode : releaseNodes) { + if (CommonUtils.isNullEmptyOrWhitespace(releaseNode.getReleaseId())) { + throw new SW360Exception("releaseId cannot be null or empty"); + } + + if (releaseWithSameLevel.contains(releaseNode.getReleaseId())) { + continue; + } + releaseWithSameLevel.add(releaseNode.getReleaseId()); + updateReleaseNodeData(releaseNode, operation); + if (releaseNode.getReleaseLink() == null) { + releaseNode.setReleaseLink(Collections.emptyList()); + } + else { + List loadedReleases = new ArrayList<>(); + loadedReleases.add(releaseNode.getReleaseId()); + releaseNode.setReleaseLink(checkAndUpdateSubNodes(releaseNode.getReleaseLink(), operation, loadedReleases)); + } + uniqueDependencyNetwork.add(releaseNode); } - uniqueDependencyNetwork.add(releaseNode); } + project.setReleaseRelationNetwork(new Gson().toJson(uniqueDependencyNetwork)); + } + else { + project.setReleaseRelationNetwork(null); } - project.setReleaseRelationNetwork(new Gson().toJson(uniqueDependencyNetwork)); } - else { - project.setReleaseRelationNetwork(null); + + private ClearingRequest convertToClearingRequest(Map requestBody) { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + ClearingRequest clearingRequest = mapper.convertValue(requestBody, ClearingRequest.class); + return clearingRequest; } - } private List checkAndUpdateSubNodes(List releaseNodes, ProjectOperation operation, List loadedReleases) throws SW360Exception { List uniqueDependencyNetwork = new ArrayList<>(); @@ -3101,12 +3125,6 @@ private void updateReleaseNodeData(ReleaseNode releaseNode, ProjectOperation ope } } - private ClearingRequest convertToClearingRequest(Map requestBody) { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - ClearingRequest clearingRequest = mapper.convertValue(requestBody, ClearingRequest.class); - return clearingRequest; - } /** * Creates a clearing request for a project. @@ -3290,3 +3308,4 @@ public ResponseEntity createDuplicateProjectWithDependencyNetwork( return ResponseEntity.created(location).body(projectDTOHalResource); } } +