Skip to content

Commit

Permalink
feat(rest): saveUsages in project page
Browse files Browse the repository at this point in the history
Signed-off-by: Rudra Chopra <prabhuchopra@gmail.com>
  • Loading branch information
rudra-superrr committed Sep 2, 2024
1 parent 4d902af commit a961a94
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 12 deletions.
24 changes: 24 additions & 0 deletions rest/resource-server/src/docs/asciidoc/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,30 @@ include::{snippets}/should_document_get_attachment_usage_for_project/curl-reques
===== Example response
include::{snippets}/should_document_get_attachment_usage_for_project/http-response.adoc[]

[[resources-project-save-attachmentUsages]]
==== Save attachmentUsages of the project

A `POST` request is used to save attachmentUsages of the project. Please pass a Map<String, List<String>> having string as key and list<string> as value in request body.

===== Request structure 1
Pass a Map<String, List<String>> in request body.

Keys can be selected, deselected, selectedConcludedUsages or deselectedConcludedUsages.

Format of String inside list varies according to the usageFields (sourcePackage, licenseInfo, manuallySet) :

for `sourcePackage`, releaseId_sourcePakage_attachmentContentId

for `licenseInfo`, projectId-releaseId_licenseInfo_attachmentContentId (for directly linked project) OR projectPath-releaseId_licenseInfo_attachmentContentId (for nested projects)

for `manuallySet`, releaseId_manuallySet_attachmentContentId

===== Example request 1
include::{snippets}/should_document_save_usages/curl-request.adoc[]

===== Example response 1
include::{snippets}/should_document_save_usages/http-response.adoc[]

[[resources-project-get-project-vulnerabilities]]
==== Listing project vulnerabilities

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.eclipse.sw360.datahandler.thrift.Source;
import org.eclipse.sw360.datahandler.thrift.attachments.Attachment;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentContent;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentService;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentUsage;
import org.eclipse.sw360.datahandler.thrift.attachments.UsageData;
Expand Down Expand Up @@ -1555,6 +1556,87 @@ public ResponseEntity<CollectionModel<EntityModel<Project>>> getUsedByProjectDet
return new ResponseEntity<>(resources, status);
}

@PreAuthorize("hasAuthority('WRITE')")
@Operation(
summary = "save attachment usages",
description = "Pass an array of string in request body.",
tags = {"Projects"}
)
@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.",
example = "{\"selected\": [\"4427a8e723ad405db63f75170ef240a2_sourcePackage_5c5d6f54ac6a4b33bcd3c5d3a8fefc43\", \"value2\"],"
+ " \"deselected\": [\"de213309ba0842ac8a7251bf27ea8f36_manuallySet_eec66c3465f64f0292dfc2564215c681\", \"value2\"],"
+ " \"selectedConcludedUsages\": [\"de213309ba0842ac8a7251bf27ea8f36_licenseInfo_eec66c3465f64f0292dfc2564215c681\", \"value2\"],"
+ " \"deselectedConcludedUsages\": [\"ade213309ba0842ac8a7251bf27ea8f36_licenseInfo_aeec66c3465f64f0292dfc2564215c681\", \"value2\"]}"
)
@RequestBody Map<String,List<String>> 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<String> selectedUsages = new ArrayList<>();
List<String> deselectedUsages = new ArrayList<>();
List<String> selectedConcludedUsages = new ArrayList<>();
List<String> deselectedConcludedUsages = new ArrayList<>();
List<String> changedUsages = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : allUsages.entrySet()) {
String key = entry.getKey();
List<String> list = entry.getValue();
if (key.equals("selected")) {
selectedUsages.addAll(list);
} else if (key.equals("deselected")) {
deselectedUsages.addAll(list);
} else if (key.equals("selectedConcludedUsages")) {
selectedConcludedUsages.addAll(list);
} else if (key.equals("deselectedConcludedUsages")) {
deselectedConcludedUsages.addAll(list);
}
}
Set<String> 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<AttachmentUsage> allUsagesByProject = projectService.getUsedAttachments(usedBy, null);
List<String> savedUsages = projectService.savedUsages(allUsagesByProject);
savedUsages.removeAll(deselectedUsages);
deselectedUsages.addAll(selectedUsages);
selectedUsages.addAll(savedUsages);
deselectedConcludedUsages.addAll(selectedConcludedUsages);
List<AttachmentUsage> deselectedUsagesFromRequest = projectService.deselectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id);
List<AttachmentUsage> selectedUsagesFromRequest = projectService.selectedAttachmentUsagesFromRequest(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, id);
List<AttachmentUsage> usagesToDelete = allUsagesByProject.stream()
.filter(usage -> deselectedUsagesFromRequest.stream()
.anyMatch(projectService.isUsageEquivalent(usage)))
.collect(Collectors.toList());
if (!usagesToDelete.isEmpty()) {
projectService.deleteAttachmentUsages(usagesToDelete);
}
List<AttachmentUsage> allUsagesByProjectAfterCleanUp = projectService.getUsedAttachments(usedBy, null);
List<AttachmentUsage> 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);
}
}

@Operation(
description = "Get all attachmentUsages of the projects.",
tags = {"Projects"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,8 @@
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
import org.eclipse.sw360.datahandler.thrift.Source;
import org.eclipse.sw360.datahandler.thrift.ThriftClients;
import org.eclipse.sw360.datahandler.thrift.attachments.*;
import org.eclipse.sw360.datahandler.thrift.components.ComponentService;
import org.eclipse.sw360.datahandler.thrift.attachments.Attachment;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType;
import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus;
import org.eclipse.sw360.datahandler.thrift.attachments.*;
import org.eclipse.sw360.datahandler.thrift.components.Release;
import org.eclipse.sw360.datahandler.thrift.components.ReleaseClearingStatusData;
import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink;
Expand All @@ -63,6 +60,7 @@
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
import org.eclipse.sw360.rest.resourceserver.release.ReleaseController;
import org.eclipse.sw360.rest.resourceserver.release.Sw360ReleaseService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
Expand Down Expand Up @@ -104,6 +102,7 @@
import java.util.stream.Collectors;

import static com.google.common.base.Strings.nullToEmpty;
import static org.eclipse.sw360.datahandler.common.CommonUtils.arrayToList;
import static org.eclipse.sw360.datahandler.common.CommonUtils.getSortedMap;
import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace;
import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyList;
Expand Down Expand Up @@ -156,6 +155,177 @@ public Project getProjectForUserById(String projectId, User sw360User) throws TE
}
}

public boolean validate(List<String> changedUsages, User sw360User, Sw360ReleaseService releaseService, Set<String> totalReleaseIds) throws TException {
for (String data : changedUsages) {
String releaseId;
String usageData;
String attachmentContentId;
String[] parts = data.split("-");
if (parts.length > 1) {
String[] components = parts[1].split("_");
releaseId = components[0];
usageData = components[1];
attachmentContentId = components[2];
}
else {
String[] components = data.split("_");
releaseId = components[0];
usageData = components[1];
attachmentContentId = components[2];
}
boolean relPresent = totalReleaseIds.contains(releaseId);
if (!relPresent) {
return false;
}
Release release = releaseService.getReleaseForUserById(releaseId, sw360User);
Set<Attachment> attachments = release.getAttachments();
if (usageData.equals("sourcePackage")) {
for (Attachment attach : attachments) {
if (attach.getAttachmentContentId().equals(attachmentContentId) && (attach.getAttachmentType() != AttachmentType.SOURCE && attach.getAttachmentType() != AttachmentType.SOURCE_SELF)) {
return false;
}
}
}
if (usageData.equals("licenseInfo")) {
for (Attachment attach : attachments) {
if (attach.getAttachmentContentId().equals(attachmentContentId) && (attach.getAttachmentType() != AttachmentType.COMPONENT_LICENSE_INFO_COMBINED && attach.getAttachmentType() != AttachmentType.COMPONENT_LICENSE_INFO_XML)) {
return false;
}
}
}
} return true;
}

public List<String> savedUsages(List<AttachmentUsage> allUsagesByProject) {
List<String> selectedData = new ArrayList<>();
for (AttachmentUsage usage : allUsagesByProject) {
if (usage.getUsageData().getSetField().equals(UsageData._Fields.LICENSE_INFO)) {
StringBuilder result = new StringBuilder();
result.append(usage.getUsageData().getLicenseInfo().getProjectPath())
.append("-")
.append(usage.getOwner().getReleaseId())
.append("_")
.append(usage.getUsageData().getSetField().getFieldName())
.append("_")
.append(usage.getAttachmentContentId());
String stringResult = result.toString();
selectedData.add(stringResult);
}
else {
StringBuilder result = new StringBuilder();
result.append(usage.getOwner().getReleaseId())
.append("_")
.append(usage.getUsageData().getSetField().getFieldName())
.append("_")
.append(usage.getAttachmentContentId());
String stringResult = result.toString();
selectedData.add(stringResult);
}
}
System.out.println("Resulting string: " + selectedData);
return selectedData;
}

public List<AttachmentUsage> deselectedAttachmentUsagesFromRequest(List<String> deselectedUsages, List<String> selectedUsages, List<String> deselectedConcludedUsages, List<String> selectedConcludedUsages, String id) {
return makeAttachmentUsagesFromParameters(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, Sets::difference, true, id);
}

public List<AttachmentUsage> selectedAttachmentUsagesFromRequest(List<String> deselectedUsages, List<String> selectedUsages, List<String> deselectedConcludedUsages, List<String> selectedConcludedUsages, String id) {
return makeAttachmentUsagesFromParameters(deselectedUsages, selectedUsages, deselectedConcludedUsages, selectedConcludedUsages, Sets::intersection, false, id);
}

private static List<AttachmentUsage> makeAttachmentUsagesFromParameters(List<String> deselectedUsages, List<String> selectedUsage,
List<String> deselectedConcludedUsages, List<String> selectedConcludedUsages,
BiFunction<Set<String>, Set<String>, Set<String>> computeUsagesFromCheckboxes, boolean deselectUsage, String projectId) {
Set<String> selectedUsages = new HashSet<>(selectedUsage);
Set<String> changedUsages = new HashSet<>(deselectedUsages);
Set<String> changedIncludeConludedLicenses = new HashSet<>(deselectedConcludedUsages);
changedUsages = Sets.union(changedUsages, new HashSet(changedIncludeConludedLicenses));
List<String> includeConludedLicenses = new ArrayList<>(selectedConcludedUsages);
Set<String> usagesSubset = computeUsagesFromCheckboxes.apply(changedUsages, selectedUsages);
if (deselectUsage) {
usagesSubset = Sets.union(usagesSubset, new HashSet(changedIncludeConludedLicenses));
}
return usagesSubset.stream()
.map(s -> parseAttachmentUsageFromString(projectId, s, includeConludedLicenses))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private static AttachmentUsage parseAttachmentUsageFromString(String projectId, String s, List<String> includeConludedLicense) {
String[] split = s.split("_");
if (split.length != 3) {
log.warn(String.format("cannot parse attachment usage from %s for project id %s", s, projectId));
return null;
}

String releaseId = split[0];
String type = split[1];
String attachmentContentId = split[2];
String projectPath = null;
if (UsageData._Fields.findByName(type).equals(UsageData._Fields.LICENSE_INFO)) {
String[] projectPath_releaseId = split[0].split("-");
if (projectPath_releaseId.length == 2) {
releaseId = projectPath_releaseId[1];
projectPath = projectPath_releaseId[0];
}
}

AttachmentUsage usage = new AttachmentUsage(Source.releaseId(releaseId), attachmentContentId, Source.projectId(projectId));
final UsageData usageData;
switch (UsageData._Fields.findByName(type)) {
case LICENSE_INFO:
LicenseInfoUsage licenseInfoUsage = new LicenseInfoUsage(Collections.emptySet());
licenseInfoUsage.setIncludeConcludedLicense(includeConludedLicense.contains(s));
if (projectPath != null) {
licenseInfoUsage.setProjectPath(projectPath);
}
usageData = UsageData.licenseInfo(licenseInfoUsage);
break;
case SOURCE_PACKAGE:
usageData = UsageData.sourcePackage(new SourcePackageUsage());
break;
case MANUALLY_SET:
usageData = UsageData.manuallySet(new ManuallySetUsage());
break;
default:
throw new IllegalArgumentException("Unexpected UsageData type: " + type);
}
usage.setUsageData(usageData);
return usage;
}

/**
* Here, "equivalent" means an AttachmentUsage should replace another one in the DB, not that they are equal.
* I.e, they have the same attachmentContentId, owner, usedBy, and same UsageData type.
*/
@NotNull
public Predicate<AttachmentUsage> isUsageEquivalent(AttachmentUsage usage) {
return equivalentUsage -> usage.getAttachmentContentId().equals(equivalentUsage.getAttachmentContentId()) &&
usage.getOwner().equals(equivalentUsage.getOwner()) &&
usage.getUsedBy().equals(equivalentUsage.getUsedBy()) &&
usage.getUsageData().getSetField().equals(equivalentUsage.getUsageData().getSetField());
}

public void deleteAttachmentUsages(List<AttachmentUsage> usagesToDelete) throws TException {
ThriftClients thriftClients = new ThriftClients();
AttachmentService.Iface attachmentClient = thriftClients.makeAttachmentClient();
attachmentClient.deleteAttachmentUsages(usagesToDelete);
}

public void makeAttachmentUsages(List<AttachmentUsage> usagesToCreate) throws TException {
ThriftClients thriftClients = new ThriftClients();
AttachmentService.Iface attachmentClient = thriftClients.makeAttachmentClient();
attachmentClient.makeAttachmentUsages(usagesToCreate);
}

public List<AttachmentUsage> getUsedAttachments(Source usedBy, Object object) throws TException {
ThriftClients thriftClients = new ThriftClients();
AttachmentService.Iface attachmentClient = thriftClients.makeAttachmentClient();
List<AttachmentUsage> allUsagesByProjectAfterCleanUp = attachmentClient.getUsedAttachments(usedBy, null);
return allUsagesByProjectAfterCleanUp;
}

public String getCyclicLinkedProjectPath(Project project, User user) throws TException {
ProjectService.Iface sw360ProjectClient = getThriftProjectClient();
String cyclicLinkedProjectPath = sw360ProjectClient.getCyclicLinkedProjectPath(project, user);
Expand Down
Loading

0 comments on commit a961a94

Please sign in to comment.