From 1c4b223f835c505bbf800f455413a3aaf144acf3 Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Thu, 25 May 2023 17:37:44 +0700 Subject: [PATCH 01/17] feat(update):update response api get attachment by component Signed-off-by: tuannn2 --- .../db/AttachmentDatabaseHandler.java | 4 + .../db/AttachmentUsageRepository.java | 8 + .../sw360/attachments/AttachmentHandler.java | 6 + .../src/main/thrift/attachments.thrift | 54 +++++ .../attachment/Sw360AttachmentService.java | 184 ++++++++++++++++-- .../component/ComponentController.java | 5 +- .../core/JacksonCustomizations.java | 65 +++++++ .../core/RestControllerHelper.java | 24 +++ .../restdocs/ComponentSpecTest.java | 45 ++++- 9 files changed, 370 insertions(+), 25 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentDatabaseHandler.java index 77224ab450..7bd083d5c2 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentDatabaseHandler.java @@ -236,6 +236,10 @@ public List getUsedAttachments(Source usedBy, UsageData filter) } } + public List getUsedAttachmentsById(String attachmentId) { + return attachmentUsageRepository.getUsedAttachmentById(attachmentId); + } + public Map, Integer> getAttachmentUsageCount(Map> attachments, UsageData filter) { Map idToType = Maps.newHashMap(); Map> queryFor = attachments.entrySet().stream() diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentUsageRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentUsageRepository.java index 56911e16b0..a828d3dba0 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentUsageRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentUsageRepository.java @@ -38,6 +38,7 @@ public class AttachmentUsageRepository extends DatabaseRepositoryCloudantClient { private static final String USAGESBYATTACHMENT = "function(doc) { if (doc.type == 'attachmentUsage') emit([doc.owner.value_, doc.attachmentContentId], null); }"; private static final String USEDATTACHMENTS = "function(doc) { if (doc.type == 'attachmentUsage') emit(doc.usedBy.value_, null); }"; + private static final String USEDATTACHMENTBYID = "function(doc) { if (doc.type == 'attachmentUsage') emit(doc.attachmentContentId, doc._id); }"; private static final String USAGESBYATTACHMENTUSAGETYPE = "function(doc) { if (doc.type == 'attachmentUsage') emit([doc.owner.value_, doc.attachmentContentId, doc.usageData != null ? doc.usageData.setField_ : null], null); }"; private static final String USEDATTACHMENTUSAGESTYPE = "function(doc) { if (doc.type == 'attachmentUsage') emit([doc.usedBy.value_, doc.usageData != null ? doc.usageData.setField_ : null], null); }"; private static final String REFERENCES_RELEASEID = "" + @@ -58,6 +59,7 @@ public AttachmentUsageRepository(DatabaseConnectorCloudant db) { views.put("all", createMapReduce(ALL, null)); views.put("usagesByAttachment", createMapReduce(USAGESBYATTACHMENT, "_count")); views.put("usedAttachments", createMapReduce(USEDATTACHMENTS, "_count")); + views.put("usedAttachmentById", createMapReduce(USEDATTACHMENTBYID, null)); views.put("usagesByAttachmentUsageType", createMapReduce(USAGESBYATTACHMENTUSAGETYPE, "_count")); views.put("usedAttachmentsUsageType", createMapReduce(USEDATTACHMENTUSAGESTYPE, "_count")); views.put("referencesReleaseId", createMapReduce(REFERENCES_RELEASEID, null)); @@ -76,6 +78,12 @@ public List getUsedAttachments(String usedById) { return queryView(reqBuilder); } + public List getUsedAttachmentById(String attachmentContentId) { + ViewRequestBuilder viewQuery = getConnector().createQuery(AttachmentUsage.class, "usedAttachmentById"); + UnpaginatedRequestBuilder reqBuilder = viewQuery.newRequest(Key.Type.STRING, Object.class).includeDocs(true).reduce(false).keys(attachmentContentId); + return queryView(reqBuilder); + } + public List getUsageForAttachment(String ownerId, String attachmentContentId, String filter) { ViewRequestBuilder viewQuery = getConnector().createQuery(AttachmentUsage.class, "usagesByAttachmentUsageType"); UnpaginatedRequestBuilder reqBuilder = viewQuery.newRequest(Key.Type.COMPLEX, Object.class).includeDocs(true).reduce(false) diff --git a/backend/src/src-attachments/src/main/java/org/eclipse/sw360/attachments/AttachmentHandler.java b/backend/src/src-attachments/src/main/java/org/eclipse/sw360/attachments/AttachmentHandler.java index 793e4b820f..749c6523d0 100644 --- a/backend/src/src-attachments/src/main/java/org/eclipse/sw360/attachments/AttachmentHandler.java +++ b/backend/src/src-attachments/src/main/java/org/eclipse/sw360/attachments/AttachmentHandler.java @@ -193,6 +193,12 @@ public List getUsedAttachments(Source usedBy, UsageData filter) return handler.getUsedAttachments(usedBy, filter); } + @Override + public List getUsedAttachmentsById(String attachmentId) throws TException { + assertNotNull(attachmentId); + return handler.getUsedAttachmentsById(attachmentId); + } + @Override public void replaceAttachmentUsages(Source usedBy, List attachmentUsages) throws TException { assertNotNull(usedBy); diff --git a/libraries/datahandler/src/main/thrift/attachments.thrift b/libraries/datahandler/src/main/thrift/attachments.thrift index 93cd08f6ae..b5aeefd6bd 100644 --- a/libraries/datahandler/src/main/thrift/attachments.thrift +++ b/libraries/datahandler/src/main/thrift/attachments.thrift @@ -74,6 +74,55 @@ struct Attachment { 23: optional string superAttachmentFilename } +struct AttachmentDTO { + // WILL NOT BE SAVED IN DB, only for api + // General information + 1: required string attachmentContentId, + 5: required string filename, + 6: optional string sha1, + + 10: optional AttachmentType attachmentType, + + 11: optional string createdBy, + 12: optional string createdTeam, + 13: optional string createdComment, + 14: optional string createdOn, + 15: optional string checkedBy, + 16: optional string checkedTeam, + 17: optional string checkedComment, + 18: optional string checkedOn, + + 20: optional set uploadHistory, + 21: optional CheckStatus checkStatus; + 22: optional string superAttachmentId, + 23: optional string superAttachmentFilename, + 24: optional UsageAttachment usageAttachment; +} + +struct UsageAttachment { + // WILL NOT BE SAVED IN DB, only for api + // General information + 1: optional string id, + 2: optional string revision, + 3: optional string type = "usage", + + 10: optional i64 visible; + 11: optional i64 restricted, + 12: optional set projectUsages +} + +struct ProjectUsage { + // WILL NOT BE SAVED IN DB, only for api + // General information + 1: optional string id, + 2: optional string revision, + 3: optional string type = "ProjectUsage", + + 10: optional string projectId; + 11: optional string projectName, + +} + struct AttachmentContent { 1: optional string id, 2: optional string revision, @@ -283,6 +332,11 @@ service AttachmentService { */ list getUsedAttachments(1: Source usedBy, 2: UsageData filter); + /** + * Returns attachments based on its Id value + */ + list getUsedAttachmentsById(1: string attachmentId); + /** * Returns attachments based on its attachmentContentId value */ diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/attachment/Sw360AttachmentService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/attachment/Sw360AttachmentService.java index ca3ae1ed8f..42728b5633 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/attachment/Sw360AttachmentService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/attachment/Sw360AttachmentService.java @@ -12,6 +12,7 @@ package org.eclipse.sw360.rest.resourceserver.attachment; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -20,6 +21,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; import org.eclipse.sw360.commonIO.AttachmentFrontendUtils; import org.eclipse.sw360.datahandler.common.CommonUtils; @@ -28,14 +32,10 @@ import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.couchdb.AttachmentConnector; 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.CheckStatus; -import org.eclipse.sw360.datahandler.thrift.attachments.LicenseInfoUsage; -import org.eclipse.sw360.datahandler.thrift.attachments.UsageData; +import org.eclipse.sw360.datahandler.thrift.ThriftUtils; +import org.eclipse.sw360.datahandler.thrift.attachments.*; +import org.eclipse.sw360.datahandler.thrift.projects.Project; +import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; import org.eclipse.sw360.rest.resourceserver.core.ThriftServiceProvider; @@ -51,20 +51,15 @@ import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyList; @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) @@ -101,6 +96,15 @@ public AttachmentInfo getAttachmentById(String id) throws TException { return createAttachmentInfo(attachmentClient, attachments.get(0)); } + public List getAttachmentUseById(String id) throws TException { + AttachmentService.Iface attachmentClient = getThriftAttachmentClient(); + List attachments = attachmentClient.getUsedAttachmentsById(id); + if (attachments.isEmpty()) { + throw new ResourceNotFoundException("Attachment not found."); + } + return attachments; + } + public List getAttachmentsBySha1(String sha1) throws TException { AttachmentService.Iface attachmentClient = getThriftAttachmentClient(); List attachments = attachmentClient.getAttachmentsBySha1s(Collections.singleton(sha1)); @@ -294,6 +298,19 @@ public CollectionModel> getResourcesFromList(Set> getAttachmentDTOResourcesFromList(User user, Set attachments, Source owner) throws TTransportException { + Map attachmentUsageAttachmentMap = getAttachmentUsages(user, attachments, owner); + Set attachmentDTOs = getAttachmentDTOs(attachments, attachmentUsageAttachmentMap); + final List> attachmentResources = new ArrayList<>(); + if (CommonUtils.isNotEmpty(attachmentDTOs)) { + for (final AttachmentDTO attachment : attachmentDTOs) { + final EntityModel attachmentResource = EntityModel.of(attachment); + attachmentResources.add(attachmentResource); + } + } + return CollectionModel.of(attachmentResources); + } + public List getAllAttachmentUsage(String projectId) throws TException { AttachmentService.Iface attachmentClient = getThriftAttachmentClient(); return attachmentClient.getUsedAttachments(Source.projectId(projectId), null); @@ -388,4 +405,139 @@ private File renameFile(File sourceFile, String filename) throws IOException { FileUtils.copyFile(sourceFile, file); return file; } + + public Map getAttachmentUsages(User user, Set attachments, Source owner) throws TTransportException { + Set atts = CommonUtils.nullToEmptySet(attachments); + Set attachmentContentIds = atts.stream().map(Attachment::getAttachmentContentId).collect(Collectors.toSet()); + Map restrictedProjectsCountsByContentId = getRestrictedProjectsCountsByContentId(attachmentContentIds, user, owner); + Map attachmentUsageMap = getAttachmentUsageMap(restrictedProjectsCountsByContentId, user); + + return attachmentUsageMap; + } + + private Map getRestrictedProjectsCountsByContentId(Set attachmentContentIds, User user, Source owner) throws TTransportException { + AttachmentService.Iface client = getThriftAttachmentClient(); + Map restrictedProjectsCountsByContentId = new HashMap<>(); + try { + Map> attachmentUsagesByContentId = + client.getAttachmentsUsages(owner, attachmentContentIds, null) + .stream() + .collect(Collectors.groupingBy(AttachmentUsage::getAttachmentContentId)); + + Map> usingProjectsByContentId = fetchUsingProjectsForAttachmentUsages(attachmentUsagesByContentId, user); + restrictedProjectsCountsByContentId = countRestrictedProjectsByContentId(attachmentUsagesByContentId, usingProjectsByContentId); + } catch (TException e) { + log.error("Cannot load restricted projects counts by contentId", e); + } + return restrictedProjectsCountsByContentId; + } + + private Map getAttachmentUsageMap(Map restrictedProjectsCountsByContentId, User user) { + Map attachmentUsageMap = new HashMap<>(); + + restrictedProjectsCountsByContentId.entrySet().stream().forEach(stringLongEntry -> { + try { + List attachmentUsages = getAttachmentUseById(stringLongEntry.getKey()); + + Attachment attachment = getAttachmentForId(stringLongEntry.getKey()); + Set projectUsages = getProjectAttachmentUsages(attachmentUsages, user); + long numberProjectByAttachmentUsages = distinctProjectIdsFromAttachmentUsages(attachmentUsages).count(); + + UsageAttachment usage = new UsageAttachment(); + usage.setVisible(numberProjectByAttachmentUsages); + usage.setRestricted(stringLongEntry.getValue()); + usage.setProjectUsages(projectUsages); + + attachmentUsageMap.put(attachment,usage); + } catch (TException e) { + log.error("Cannot load map attachment usages", e); + } + }); + return attachmentUsageMap; + } + + private Set getProjectAttachmentUsages(List attachmentUsages, User user) { + Set projectUsages = new HashSet<>(); + attachmentUsages.stream().forEach(attachmentUsage -> { + try { + Project project = getThriftProjectClient().getProjectById(attachmentUsage.getUsedBy().getProjectId(), user); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(project.getName()); + stringBuilder.append("("); + stringBuilder.append(project.getVersion()); + stringBuilder.append(")"); + + ProjectUsage projectUsage = new ProjectUsage(); + projectUsage.setProjectId(attachmentUsage.getUsedBy().getProjectId()); + projectUsage.setProjectName(stringBuilder.toString()); + + projectUsages.add(projectUsage); + } catch (TException e) { + log.error("Cannot load project name attachment usages", e); + } + }); + return projectUsages; + } + + private Map> fetchUsingProjectsForAttachmentUsages(Map> attachmentUsagesByContentId, User user) throws TException { + List projectIds = attachmentUsagesByContentId.values().stream() + .flatMap(Collection::stream) + .map(AttachmentUsage::getUsedBy) + .map(Source::getProjectId) + .distinct().collect(Collectors.toList()); + + List usingProjectsList = getThriftProjectClient().getProjectsById(projectIds, user); + Map usingProjects = ThriftUtils.getIdMap(usingProjectsList); + return Maps.transformValues( + attachmentUsagesByContentId, + attUsages -> distinctProjectIdsFromAttachmentUsages(attUsages) + .map(usingProjects::get) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + + private ProjectService.Iface getThriftProjectClient() throws TTransportException { + THttpClient thriftClient = new THttpClient(thriftServerUrl + "/projects/thrift"); + TProtocol protocol = new TCompactProtocol(thriftClient); + return new ProjectService.Client(protocol); + } + + private Stream distinctProjectIdsFromAttachmentUsages (List usages){ + return nullToEmptyList(usages).stream() + .map(AttachmentUsage::getUsedBy) + .map(Source::getProjectId) + .distinct(); + } + + private Map countRestrictedProjectsByContentId(Map> attachmentUsagesByContentId, Map> usingProjectsByContentId) { + return attachmentUsagesByContentId.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, + entry -> distinctProjectIdsFromAttachmentUsages(entry.getValue()) + .count() - usingProjectsByContentId.get(entry.getKey()).size() + )); + } + + public Attachment getAttachmentForId(String id) throws TException { + AttachmentService.Iface attachmentClient = getThriftAttachmentClient(); + List attachments = attachmentClient.getAttachmentsByIds(Collections.singleton(id)); + if (attachments.isEmpty()) { + throw new ResourceNotFoundException("Attachment not found."); + } + return attachments.get(0); + } + + public Set getAttachmentDTOs(Set attachments, Map attachmentUsages ) { + Set attachmentDTOS = new HashSet<>(); + attachmentUsages.entrySet().stream().forEach(attachmentUsageEntry -> { + attachments.remove(attachmentUsageEntry.getKey()); + AttachmentDTO attachmentDTO = restControllerHelper.convertAttachmentToAttachmentDTO(attachmentUsageEntry.getKey(),attachmentUsageEntry.getValue()); + attachmentDTOS.add(attachmentDTO); + }); + attachments.forEach(attachment -> { + AttachmentDTO attachmentDTO = restControllerHelper.convertAttachmentToAttachmentDTO(attachment,new UsageAttachment()); + attachmentDTOS.add(attachmentDTO); + }); + return attachmentDTOS; + } } 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 ed7f0a558b..5742fffced 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 @@ -25,6 +25,7 @@ import org.eclipse.sw360.datahandler.thrift.Source; import org.eclipse.sw360.datahandler.thrift.VerificationStateInfo; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; @@ -297,11 +298,11 @@ public ResponseEntity> createComponent(@RequestBody Compo } @RequestMapping(value = COMPONENTS_URL + "/{id}/attachments", method = RequestMethod.GET) - public ResponseEntity>> getComponentAttachments( + public ResponseEntity>> getComponentAttachments( @PathVariable("id") String id) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Component sw360Component = componentService.getComponentForUserById(id, sw360User); - final CollectionModel> resources = attachmentService.getResourcesFromList(sw360Component.getAttachments()); + final CollectionModel> resources = attachmentService.getAttachmentDTOResourcesFromList(sw360User, sw360Component.getAttachments(), Source.releaseId(sw360Component.getId())); return new ResponseEntity<>(resources, HttpStatus.OK); } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 3cc6598f5a..50889035a6 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -23,6 +23,9 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.eclipse.sw360.datahandler.thrift.*; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; +import org.eclipse.sw360.datahandler.thrift.attachments.ProjectUsage; +import org.eclipse.sw360.datahandler.thrift.attachments.UsageAttachment; import org.eclipse.sw360.datahandler.thrift.changelogs.ChangeLogs; import org.eclipse.sw360.datahandler.thrift.changelogs.ChangedFields; import org.eclipse.sw360.datahandler.thrift.changelogs.ReferenceDocData; @@ -67,6 +70,9 @@ public Sw360Module() { setMixInAnnotation(ReleaseLink.class, Sw360Module.ReleaseLinkMixin.class); setMixInAnnotation(ClearingReport.class, Sw360Module.ClearingReportMixin.class); setMixInAnnotation(Attachment.class, Sw360Module.AttachmentMixin.class); + setMixInAnnotation(AttachmentDTO.class, Sw360Module.AttachmentDTOMixin.class); + setMixInAnnotation(UsageAttachment.class, Sw360Module.UsageAttachmentMixin.class); + setMixInAnnotation(ProjectUsage.class, Sw360Module.ProjectUsageMixin.class); setMixInAnnotation(Vendor.class, Sw360Module.VendorMixin.class); setMixInAnnotation(License.class, Sw360Module.LicenseMixin.class); setMixInAnnotation(Obligation.class, Sw360Module.ObligationMixin.class); @@ -577,6 +583,65 @@ static abstract class ClearingReportMixin { static abstract class AttachmentMixin { } + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties({ + "setAttachmentContentId", + "setFilename", + "setSha1", + "setAttachmentType", + "setCreatedBy", + "setCreatedTeam", + "setCreatedComment", + "setCreatedOn", + "setCheckedBy", + "setCheckedTeam", + "setCheckedComment", + "setCheckedOn", + "uploadHistorySize", + "uploadHistoryIterator", + "setUploadHistory", + "setCheckStatus", + "setSuperAttachmentId", + "setSuperAttachmentFilename", + "setUsageAttachment" + }) + static abstract class AttachmentDTOMixin { + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties({ + "id", + "revision", + "type", + "setVisible", + "setRestricted", + "projectNameSize", + "projectNameIterator", + "setProjectName", + "setRevision", + "setType", + "setId", + "projectUsagesSize", + "projectUsagesIterator", + "setProjectUsages", + }) + static abstract class UsageAttachmentMixin { + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties({ + "id", + "revision", + "type", + "setRevision", + "setType", + "setProjectId", + "setId", + "setProjectName" + }) + static abstract class ProjectUsageMixin { + } + @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties({ "id", 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 0c7e89a047..0211066e4d 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 @@ -31,6 +31,8 @@ import org.eclipse.sw360.datahandler.thrift.Quadratic; import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; +import org.eclipse.sw360.datahandler.thrift.attachments.UsageAttachment; import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; @@ -658,6 +660,28 @@ public Attachment convertToEmbeddedAttachment(Attachment attachment) { return attachment; } + public AttachmentDTO convertAttachmentToAttachmentDTO(Attachment attachment, UsageAttachment usage) { + AttachmentDTO attachmentDTO = new AttachmentDTO(); + attachmentDTO.setAttachmentContentId(attachment.getAttachmentContentId()); + attachmentDTO.setFilename(attachment.getFilename()); + attachmentDTO.setSha1(attachment.getSha1()); + attachmentDTO.setAttachmentType(attachment.getAttachmentType()); + attachmentDTO.setCreatedBy(attachment.getCreatedBy()); + attachmentDTO.setCreatedTeam(attachment.getCreatedTeam()); + attachmentDTO.setCreatedComment(attachment.getCreatedComment()); + attachmentDTO.setCreatedOn(attachment.getCreatedOn()); + attachmentDTO.setCheckedBy(attachment.getCheckedBy()); + attachmentDTO.setCheckedTeam(attachment.getCheckedTeam()); + attachmentDTO.setCheckedComment(attachment.getCheckedComment()); + attachmentDTO.setCheckedOn(attachment.getCheckedOn()); + attachmentDTO.setCheckStatus(attachment.getCheckStatus()); + attachmentDTO.setSuperAttachmentId(attachment.getSuperAttachmentId()); + attachmentDTO.setSuperAttachmentFilename(attachment.getSuperAttachmentFilename()); + attachmentDTO.setUsageAttachment(usage); + + return attachmentDTO; + } + public Vulnerability convertToEmbeddedVulnerability(Vulnerability vulnerability) { Vulnerability embeddedVulnerability = new Vulnerability(vulnerability.getExternalId()); embeddedVulnerability.setId(vulnerability.getId()); diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java index bf1de0244d..4d80f58c18 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java @@ -20,11 +20,8 @@ import org.eclipse.sw360.datahandler.thrift.VerificationState; import org.eclipse.sw360.datahandler.thrift.VerificationStateInfo; import org.eclipse.sw360.datahandler.thrift.RequestSummary; -import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; -import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentContent; -import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; -import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.components.*; +import org.eclipse.sw360.datahandler.thrift.attachments.*; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectType; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -183,6 +180,29 @@ public void before() throws TException, IOException { componentList.add(angularComponent); componentListByName.add(angularComponent); + AttachmentDTO attachmentDTO = new AttachmentDTO(); + attachmentDTO.setAttachmentContentId(""); + attachmentDTO.setFilename(attachment.getFilename()); + attachmentDTO.setSha1(attachment.getSha1()); + attachmentDTO.setAttachmentType(AttachmentType.BINARY_SELF); + attachmentDTO.setCreatedBy("admin@sw360.org"); + attachmentDTO.setCreatedTeam("Clearing Team 1"); + attachmentDTO.setCreatedComment("please check asap"); + attachmentDTO.setCreatedOn("2016-12-18"); + attachmentDTO.setCheckedTeam("Clearing Team 2"); + attachmentDTO.setCheckedComment("everything looks good"); + attachmentDTO.setCheckedOn("2016-12-18"); + attachmentDTO.setCheckStatus(CheckStatus.ACCEPTED); + + UsageAttachment usageAttachment = new UsageAttachment(); + usageAttachment.setVisible(0); + usageAttachment.setRestricted(0); + + attachmentDTO.setUsageAttachment(usageAttachment); + List> atEntityModels = new ArrayList<>(); + atEntityModels.add(EntityModel.of(attachmentDTO)); + given(this.attachmentServiceMock.getAttachmentDTOResourcesFromList(any(), any(), any())).willReturn(CollectionModel.of(atEntityModels)); + Component springComponent = new Component(); Map springComponentExternalIds = new HashMap<>(); springComponentExternalIds.put("component-id-key", "c77321"); @@ -836,9 +856,20 @@ public void should_document_get_component_attachment_info() throws Exception { .andExpect(status().isOk()) .andDo(this.documentationHandler.document( responseFields( - subsectionWithPath("_embedded.sw360:attachments").description("An array of <>"), - subsectionWithPath("_embedded.sw360:attachments.[]filename").description("The attachment filename"), - subsectionWithPath("_embedded.sw360:attachments.[]sha1").description("The attachment sha1 value"), + subsectionWithPath("_embedded.sw360:attachmentDTOes").description("An array of <>"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]attachmentContentId").description("The attachment attachmentContentId"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]filename").description("The attachment filename"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]sha1").description("The attachment sha1"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]attachmentType").description("The attachment attachmentType"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdBy").description("The attachment createdBy"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdTeam").description("The attachment createdTeam"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdComment").description("The attachment createdComment"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdOn").description("The attachment createdOn"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]checkedComment").description("The attachment checkedComment"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]checkStatus").description("The attachment checkStatus"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment").description("The usages in project"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment.visible").description("The visible usages in project"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment.restricted").description("The restricted usages in project"), subsectionWithPath("_links").description("<> to other resources") ))); } From c27a2fe35d6e30eebabca8e55393110a36f99a7b Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Fri, 9 Jun 2023 14:55:17 +0700 Subject: [PATCH 02/17] feat(rest): update response API Listing vendors Signed-off-by: tuannn2 --- .../sw360/rest/resourceserver/core/RestControllerHelper.java | 2 ++ .../sw360/rest/resourceserver/restdocs/VendorSpecTest.java | 2 ++ 2 files changed, 4 insertions(+) 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 0c7e89a047..1f87aa1810 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 @@ -635,6 +635,8 @@ public Obligation convertToEmbeddedObligation(Obligation obligation) { public Vendor convertToEmbeddedVendor(Vendor vendor) { Vendor embeddedVendor = convertToEmbeddedVendor(vendor.getFullname()); embeddedVendor.setId(vendor.getId()); + embeddedVendor.setShortname(vendor.getShortname()); + embeddedVendor.setUrl(vendor.getUrl()); return embeddedVendor; } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java index 80f72c79df..8dd029262e 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java @@ -91,6 +91,8 @@ public void should_document_get_vendors() throws Exception { ), responseFields( subsectionWithPath("_embedded.sw360:vendors.[]fullName").description("The full name of the vendor"), + subsectionWithPath("_embedded.sw360:vendors.[]shortName").description("The Short Name of the vendor"), + subsectionWithPath("_embedded.sw360:vendors.[]url").description("The Url of the vendor"), subsectionWithPath("_embedded.sw360:vendors").description("An array of <>"), subsectionWithPath("_links").description("<> to other resources") ))); From f14f9b0e451cc962a1f4dcd814ad8d963349e838 Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Fri, 9 Jun 2023 17:07:04 +0700 Subject: [PATCH 03/17] feat(rest): update response API Listing users Signed-off-by: tuannn2 --- .../resourceserver/core/RestControllerHelper.java | 12 ++++++++++++ .../rest/resourceserver/user/UserController.java | 2 +- .../rest/resourceserver/restdocs/UserSpecTest.java | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) 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 0c7e89a047..2398ca3565 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 @@ -603,6 +603,18 @@ public User convertToEmbeddedUser(User user) { return embeddedUser; } + public User convertToEmbeddedGetUsers(User user) { + User embeddedUser = new User(); + embeddedUser.setId(user.getId()); + embeddedUser.setFullname(user.getFullname()); + embeddedUser.setEmail(user.getEmail()); + embeddedUser.setGivenname(user.getGivenname()); + embeddedUser.setLastname(user.getLastname()); + embeddedUser.setDepartment(user.getDepartment()); + embeddedUser.setType(null); + return embeddedUser; + } + public void addEmbeddedObligations(HalResource halLicense, List obligations) { for (Obligation obligation : obligations) { HalResource obligationHalResource = addEmbeddedObligation(obligation); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java index db915ac1bb..67506307f0 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java @@ -68,7 +68,7 @@ public ResponseEntity>> getUsers() { List> userResources = new ArrayList<>(); for (User sw360User : sw360Users) { - User embeddedUser = restControllerHelper.convertToEmbeddedUser(sw360User); + User embeddedUser = restControllerHelper.convertToEmbeddedGetUsers(sw360User); EntityModel userResource = EntityModel.of(embeddedUser); userResources.add(userResource); } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java index 31095d9cbf..532106084e 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java @@ -117,6 +117,11 @@ public void should_document_get_users() throws Exception { ), responseFields( subsectionWithPath("_embedded.sw360:users[]email").description("The user's email"), + subsectionWithPath("_embedded.sw360:users[]department").description("The user's department"), + subsectionWithPath("_embedded.sw360:users[]deactivated").description("The user's deactivated"), + subsectionWithPath("_embedded.sw360:users[]fullName").description("The user's full name"), + subsectionWithPath("_embedded.sw360:users[]givenName").description("The user's given name"), + subsectionWithPath("_embedded.sw360:users[]lastName").description("The user's last name"), subsectionWithPath("_embedded.sw360:users").description("An array of <>"), subsectionWithPath("_links").description("<> to other resources") ))); From b6d58b97944f5cba7acd6dc407b1ffa5cc9ed1c3 Mon Sep 17 00:00:00 2001 From: Kouki Hama Date: Tue, 13 Jun 2023 11:55:50 +0900 Subject: [PATCH 04/17] feat(ui): add note filed in license page --- .../licenses/db/LicenseDatabaseHandler.java | 1 + .../rest/resource/licenses/SW360License.java | 16 ++++++++++++++-- .../portlets/licenses/LicensesPortlet.java | 2 ++ .../html/licenses/includes/detailSummary.jspf | 4 ++++ .../licenses/includes/editDetailSummary.jspf | 9 +++++++++ .../main/resources/content/Language.properties | 2 ++ .../resources/content/Language_ja.properties | 2 ++ .../resources/content/Language_vi.properties | 2 ++ .../datahandler/src/main/thrift/licenses.thrift | 1 + .../core/JacksonCustomizations.java | 1 + .../resourceserver/restdocs/LicenseSpecTest.java | 5 ++++- 11 files changed, 42 insertions(+), 3 deletions(-) diff --git a/backend/src/src-licenses/src/main/java/org/eclipse/sw360/licenses/db/LicenseDatabaseHandler.java b/backend/src/src-licenses/src/main/java/org/eclipse/sw360/licenses/db/LicenseDatabaseHandler.java index 68431a597c..da6363ba33 100644 --- a/backend/src/src-licenses/src/main/java/org/eclipse/sw360/licenses/db/LicenseDatabaseHandler.java +++ b/backend/src/src-licenses/src/main/java/org/eclipse/sw360/licenses/db/LicenseDatabaseHandler.java @@ -515,6 +515,7 @@ private License updateLicenseFromInputLicense(Optional oldLicense, Lice license.setExternalLicenseLink(inputLicense.getExternalLicenseLink()); license.setChecked(inputLicense.isChecked()); license.setObligationDatabaseIds(inputLicense.getObligationDatabaseIds()); + license.setNote(inputLicense.getNote()); return license; } diff --git a/clients/client/src/main/java/org/eclipse/sw360/clients/rest/resource/licenses/SW360License.java b/clients/client/src/main/java/org/eclipse/sw360/clients/rest/resource/licenses/SW360License.java index 36abfea8bb..cdebc28eaf 100644 --- a/clients/client/src/main/java/org/eclipse/sw360/clients/rest/resource/licenses/SW360License.java +++ b/clients/client/src/main/java/org/eclipse/sw360/clients/rest/resource/licenses/SW360License.java @@ -20,6 +20,7 @@ public final class SW360License extends SW360SimpleHalResource { private String text; private String shortName; private String fullName; + private String note; @JsonInclude(JsonInclude.Include.NON_NULL) public String getText() { @@ -51,6 +52,16 @@ public SW360License setFullName(String fullName) { return this; } + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getNote() { + return this.note; + } + + public SW360License setNote(String note) { + this.note = note; + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -58,12 +69,13 @@ public boolean equals(Object o) { SW360License that = (SW360License) o; return Objects.equals(text, that.text) && Objects.equals(shortName, that.shortName) && - Objects.equals(fullName, that.fullName); + Objects.equals(fullName, that.fullName) && + Objects.equals(note, that.note); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), text, shortName, fullName); + return Objects.hash(super.hashCode(), text, shortName, fullName, note); } @Override diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/licenses/LicensesPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/licenses/LicensesPortlet.java index 7820e058bb..f2f1f160b3 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/licenses/LicensesPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/licenses/LicensesPortlet.java @@ -348,12 +348,14 @@ private License updateLicenseFromRequest(License license, ActionRequest request) boolean checked = "true".equals(request.getParameter(License._Fields.CHECKED.toString())); String licenseTypeString = request.getParameter(License._Fields.LICENSE_TYPE.toString() + LicenseType._Fields.LICENSE_TYPE.toString()); + String note = request.getParameter(License._Fields.NOTE.name()); license.setText(CommonUtils.nullToEmptyString(text)); license.setFullname(CommonUtils.nullToEmptyString(fullname)); license.setShortname((CommonUtils.nullToEmptyString(shortname))); license.setOSIApproved(osiApproved); license.setFSFLibre(fsfLibre); license.setChecked(checked); + license.setNote(note); String obligationIds = request.getParameter("obligations"); List oblIds = CommonUtils.isNotNullEmptyOrWhitespace(obligationIds) ? Arrays.asList(obligationIds.split(",")) : Lists.newArrayList(); license.setObligationDatabaseIds(Sets.newHashSet(oblIds)); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/detailSummary.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/detailSummary.jspf index d87df51524..323c4547a5 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/detailSummary.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/detailSummary.jspf @@ -99,4 +99,8 @@ + + : + + diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/editDetailSummary.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/editDetailSummary.jspf index dec393dcb1..cca4d3228d 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/editDetailSummary.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/licenses/includes/editDetailSummary.jspf @@ -93,4 +93,13 @@ + + +
+ + +
+ + diff --git a/frontend/sw360-portlet/src/main/resources/content/Language.properties b/frontend/sw360-portlet/src/main/resources/content/Language.properties index 323ef81c8a..9dc23a8895 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language.properties @@ -514,6 +514,7 @@ enter.mail.address=Enter mail address enter.mailing.list.url=Enter Mailing List Url enter.material.index.number=Enter material index number enter.name=Enter Name +enter.note=Enter Note enter.obligation.comment=Enter obligation comment enter.oidc.client.id=Enter OIDC Client Id enter.oidc.client.name=Enter OIDC Client Name @@ -900,6 +901,7 @@ not.checked=Not Checked in.analysis=In Analysis not.decided.so.far=Not decided so far not.loaded.yet=Not loaded yet +note=Note note.other.documents.might.use.the.licenses=Note: other documents might use the licenses. not.used.in.any.project.yet=not used in any project yet no.type.restriction.is.the.same.as.looking.for.all.types.even.on.types.that.are.not.in.the.list=No type restriction is the same as looking for all types, even on types that are not in the list. diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties index b656be414e..fc14637ca3 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties @@ -513,6 +513,7 @@ enter.mail.address=メールアドレスを入力 enter.mailing.list.url=メーリングリストのURLを入力 enter.material.index.number=部品指標番号(Material index number)を入力 enter.name=名前を入力 +enter.note=ノートを入力 enter.obligation.comment=オブリゲーションコメントを入力 enter.oidc.client.id=Enter OIDC Client Id enter.oidc.client.name=Enter OIDC Client Name @@ -899,6 +900,7 @@ not.checked=未チェック in.analysis=分析中 not.decided.so.far=今のところ未定 not.loaded.yet=まだロードされていない +note=ノート note.other.documents.might.use.the.licenses=注意: 他のドキュメントではライセンスが使用されている可能性があります。 not.used.in.any.project.yet=まだどのプロジェクトでも使用されていない no.type.restriction.is.the.same.as.looking.for.all.types.even.on.types.that.are.not.in.the.list=タイプ制限がないのは、すべてのタイプを探すのと同じ。リストにない型でも使用可。 diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties index 8f4803588c..dd79076e8a 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties @@ -515,6 +515,7 @@ enter.mail.address=Nhập địa chỉ thư enter.mailing.list.url=Nhập Url danh sách gửi thư enter.material.index.number=Nhập số chỉ mục tài liệu enter.name=Nhập tên +enter.note=Nhập ghi chú enter.obligation.comment=Nhập nghĩa vụ bình luận enter.oidc.client.id=Enter OIDC Client Id enter.oidc.client.name=Enter OIDC Client Name @@ -902,6 +903,7 @@ not.checked=Chưa được kiểm tra in.analysis=In Analysis not.decided.so.far=Không quyết định cho đến nay not.loaded.yet=Not loaded yet +note=ghi chú note.other.documents.might.use.the.licenses=Lưu ý: các tài liệu khác có thể sử dụng giấy phép. not.used.in.any.project.yet=chưa được sử dụng trong bất kỳ dự án nào no.type.restriction.is.the.same.as.looking.for.all.types.even.on.types.that.are.not.in.the.list=Không có giới hạn loại nào giống như tìm kiếm tất cả các loại, ngay cả trên các loại không có trong danh sách. diff --git a/libraries/datahandler/src/main/thrift/licenses.thrift b/libraries/datahandler/src/main/thrift/licenses.thrift index d1b7e4d8be..4ba63a836a 100644 --- a/libraries/datahandler/src/main/thrift/licenses.thrift +++ b/libraries/datahandler/src/main/thrift/licenses.thrift @@ -109,6 +109,7 @@ struct License { 6: optional LicenseType licenseType, 7: optional string licenseTypeDatabaseId, 8: optional string externalLicenseLink, + 10: optional string note, // information from external data sources 9: optional map externalIds, diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index cc17a44750..918d7e7cbb 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -637,6 +637,7 @@ static abstract class VendorMixin extends Vendor { "setChecked", "additionalDataSize", "setAdditionalData", + "setNote", }) static abstract class LicenseMixin extends License { @Override diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java index 78603496bb..8b4f07f382 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/LicenseSpecTest.java @@ -73,12 +73,14 @@ public void before() throws TException { externalIds.put("Trove", "License :: OSI Approved :: Apache Software License"); license.setExternalIds(externalIds); license.setAdditionalData(Collections.singletonMap("Key", "Value")); + license.setNote("License's Note"); License license2 = new License(); license2.setId("MIT"); license2.setFullname("The MIT License (MIT)"); license2.setShortname("MIT"); license2.setText("placeholder for the MIT license text"); + license2.setNote("License2's Note"); List licenseList = new ArrayList<>(); licenseList.add(license); @@ -150,7 +152,8 @@ public void should_document_get_license() throws Exception { subsectionWithPath("OSIApproved").description("The OSI aprroved information, possible values are: " + Arrays.asList(Quadratic.values())), fieldWithPath("FSFLibre").description("The FSF libre information, possible values are: " + Arrays.asList(Quadratic.values())), subsectionWithPath("_embedded.sw360:obligations").description("An array of <>"), - subsectionWithPath("_links").description("<> to other resources") + subsectionWithPath("_links").description("<> to other resources"), + fieldWithPath("note").description("The license's note") ))); } From 41018492839650dab8ffcd63537b054404113301 Mon Sep 17 00:00:00 2001 From: Eldrin Date: Fri, 2 Jun 2023 03:39:39 +0200 Subject: [PATCH 05/17] feat(ECC):Added pagination to ECC release list --- .../db/ComponentDatabaseHandler.java | 4 + .../datahandler/db/ReleaseRepository.java | 110 ++++++++++ .../sw360/components/ComponentHandler.java | 6 + .../sw360/portal/common/PortalConstants.java | 1 + .../sw360/portal/portlets/ecc/EccPortlet.java | 197 +++++++++++++++++- .../META-INF/resources/html/ecc/view.jsp | 55 ++--- .../src/main/thrift/components.thrift | 5 + 7 files changed, 337 insertions(+), 41 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java index 9ae1156c06..b5948ebc9a 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java @@ -1972,6 +1972,10 @@ public List getAccessibleReleases(Set ids, User user) { return getAccessibleReleaseList(releaseRepository.makeSummary(SummaryType.SHORT, ids), user); } + public Map> getAccessibleReleasesWithPagination(User user, PaginationData pageData) throws TException { + return releaseRepository.getAccessibleReleasesWithPagination(user, pageData); + } + private List getAccessibleReleaseList(List releaseList, User user) { List resultList = new ArrayList(); for (Release release : releaseList) { diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java index 2b837efec9..8b7064fbfc 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java @@ -16,6 +16,7 @@ import org.eclipse.sw360.datahandler.couchdb.SummaryAwareRepository; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.PaginationData; import java.util.*; import com.cloudant.client.api.model.DesignDocument.MapReduce; @@ -27,6 +28,11 @@ import static com.google.common.base.Strings.isNullOrEmpty; +import com.google.common.collect.Maps; +import com.google.common.collect.Lists; +import com.cloudant.client.api.views.ViewRequest; +import com.cloudant.client.api.views.ViewResponse; + /** * CRUD access for the Release class * @@ -126,6 +132,40 @@ public class ReleaseRepository extends SummaryAwareRepository { " emit(doc.version.toLowerCase(), doc._id);" + " } " + "}"; + private static final String BY_STATUS_VIEW = + "function(doc) {" + + " if (doc.type == 'release') {" + + " emit(doc.eccInformation.eccStatus, doc._id);" + + " } " + + "}"; + + private static final String BY_ASSESSOR_CONTACT_PERSON_VIEW = + "function(doc) {" + + " if (doc.type == 'release') {" + + " emit(doc.eccInformation.assessorContactPerson, doc._id);" + + " } " + + "}"; + + private static final String BY_ASSESSOR_DEPARTMENT_VIEW = + "function(doc) {" + + " if (doc.type == 'release') {" + + " emit(doc.eccInformation.assessorDepartment, doc._id);" + + " } " + + "}"; + + private static final String BY_ASSESSMENT_DATE_VIEW = + "function(doc) {" + + " if (doc.type == 'release') {" + + " emit(doc.eccInformation.assessmentDate, doc._id);" + + " } " + + "}"; + + private static final String BY_CREATOR_DEPARTMENT_VIEW = + "function(doc) {" + + " if (doc.type == 'release') {" + + " emit(doc.creatorDepartment, doc._id);" + + " } " + + "}"; public ReleaseRepository(DatabaseConnectorCloudant db, VendorRepository vendorRepository) { super(Release.class, db, new ReleaseSummary(vendorRepository)); @@ -144,6 +184,11 @@ public ReleaseRepository(DatabaseConnectorCloudant db, VendorRepository vendorRe views.put("releaseByName", createMapReduce(BY_LOWERCASE_RELEASE_NAME_VIEW, null)); views.put("releaseByVersion", createMapReduce(BY_LOWERCASE_RELEASE_VERSION_VIEW, null)); views.put("releaseIdsByVendorId", createMapReduce(RELEASEIDSBYVENDORID, null)); + views.put("byStatus", createMapReduce(BY_STATUS_VIEW, null)); + views.put("byECCAssessorContactPerson", createMapReduce(BY_ASSESSOR_CONTACT_PERSON_VIEW, null)); + views.put("byECCAssessorGroup", createMapReduce(BY_ASSESSOR_DEPARTMENT_VIEW, null)); + views.put("byECCAssessmentDate", createMapReduce(BY_ASSESSMENT_DATE_VIEW, null)); + views.put("byCreatorGroup", createMapReduce(BY_CREATOR_DEPARTMENT_VIEW, null)); initStandardDesignDocument(views, db); } @@ -256,4 +301,69 @@ public Set searchByExternalIds(Map> externalIds) { public List getReferencingReleases(String releaseId) { return queryView("usedInReleaseRelation", releaseId); } + + public Map> getAccessibleReleasesWithPagination(User user, PaginationData pageData) { + final int rowsPerPage = pageData.getRowsPerPage(); + Map> result = Maps.newHashMap(); + List releases = Lists.newArrayList(); + final boolean ascending = pageData.isAscending(); + final int sortColumnNo = pageData.getSortColumnNumber(); + + ViewRequestBuilder query; + switch (sortColumnNo) { + case -1: + query = getConnector().createQuery(Release.class, "byCreatedOn"); + break; + case 0: + query = getConnector().createQuery(Release.class, "byStatus"); + break; + case 1: + query = getConnector().createQuery(Release.class, "byname"); + break; + case 2: + query = getConnector().createQuery(Release.class, "releaseByVersion"); + break; + case 3: + query = getConnector().createQuery(Release.class, "byCreatorGroup"); + break; + case 4: + query = getConnector().createQuery(Release.class, "byECCAssessorContactPerson"); + break; + case 5: + query = getConnector().createQuery(Release.class, "byECCAssessorGroup"); + break; + case 6: + query = getConnector().createQuery(Release.class, "byECCAssessmentDate"); + break; + default: + query = getConnector().createQuery(Release.class, "all"); + break; + } + + ViewRequest request = null; + if (rowsPerPage == -1) { + request = query.newRequest(Key.Type.STRING, Object.class).descending(!ascending).includeDocs(true).build(); + } else { + request = query.newPaginatedRequest(Key.Type.STRING, Object.class).rowsPerPage(rowsPerPage) + .descending(!ascending).includeDocs(true).build(); + } + + ViewResponse response = null; + try { + response = request.getResponse(); + int pageNo = pageData.getDisplayStart() / rowsPerPage; + int i = 1; + while (i <= pageNo) { + response = response.nextPage(); + i++; + } + releases = response.getDocsAs(Release.class); + } catch (Exception e) { + log.error("Error getting recent releases", e); + } + releases = makeSummaryWithPermissionsFromFullDocs(SummaryType.SUMMARY, releases, user); + pageData.setTotalRowCount(response.getTotalRowCount()); + result.put(pageData, releases); + return result; + } } diff --git a/backend/src/src-components/src/main/java/org/eclipse/sw360/components/ComponentHandler.java b/backend/src/src-components/src/main/java/org/eclipse/sw360/components/ComponentHandler.java index ee928e94d7..d311df8c59 100644 --- a/backend/src/src-components/src/main/java/org/eclipse/sw360/components/ComponentHandler.java +++ b/backend/src/src-components/src/main/java/org/eclipse/sw360/components/ComponentHandler.java @@ -120,6 +120,12 @@ public List getAccessibleReleaseSummary(User user) throws TException { return handler.getAccessibleReleaseSummary(user); } + @Override + public Map> getAccessibleReleasesWithPagination(User user, PaginationData pageData) throws TException { + assertUser(user); + return handler.getAccessibleReleasesWithPagination(user, pageData); + } + @Override public List refineSearch(String text, Map> subQueryRestrictions) throws TException { return componentSearchHandler.search(text, subQueryRestrictions); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java index 2a6fdb175f..f0065adce7 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java @@ -590,6 +590,7 @@ public class PortalConstants { public static final String UNSUBSCRIBE = "unsubscribe"; public static final String UNSUBSCRIBE_RELEASE = "unsubscribe_release"; public static final String LOAD_COMPONENT_LIST = "load_component_list"; + public static final String LOAD_ECC_LIST = "load_ecc_list"; // fossology actions public static final String FOSSOLOGY_PREFIX = "fossology"; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/ecc/EccPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/ecc/EccPortlet.java index c1fd8e2a6c..64357bbc98 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/ecc/EccPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/ecc/EccPortlet.java @@ -24,8 +24,28 @@ import java.util.Collections; import java.util.List; +import com.liferay.portal.kernel.json.JSONArray; +import org.eclipse.sw360.portal.common.datatables.PaginationParser; +import org.eclipse.sw360.portal.common.datatables.data.PaginationParameters; +import javax.servlet.http.HttpServletRequest; +import com.liferay.portal.kernel.util.PortalUtil; +import com.liferay.portal.kernel.json.JSONFactoryUtil; +import com.liferay.portal.kernel.json.JSONObject; +import org.eclipse.sw360.portal.common.*; +import org.eclipse.sw360.datahandler.thrift.*; +import java.util.*; +import com.google.common.collect.*; +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.common.*; + import javax.portlet.*; +import static org.eclipse.sw360.datahandler.common.CommonUtils.*; +import static com.liferay.portal.kernel.json.JSONFactoryUtil.createJSONArray; +import static com.liferay.portal.kernel.json.JSONFactoryUtil.createJSONObject; +import static java.lang.Math.min; +import static com.google.common.base.Strings.nullToEmpty; +import static org.eclipse.sw360.portal.common.PortalConstants.*; import static org.eclipse.sw360.portal.common.PortalConstants.ECC_PORTLET_NAME; import static org.eclipse.sw360.portal.common.PortalConstants.RELEASE_LIST; @@ -50,6 +70,15 @@ public class EccPortlet extends Sw360Portlet { private static final Logger log = LogManager.getLogger(EccPortlet.class); + // ECC release view datatables, index of columns + private static final int RELEASE_NO_SORT = -1; + private static final int RELEASE_DT_ROW_ECC_STATUS = 0; + private static final int RELEASE_DT_ROW_NAME = 1; + private static final int RELEASE_DT_ROW_VERSION = 2; + private static final int RELEASE_DT_ROW_GROUP = 3; + private static final int RELEASE_DT_ROW_ASSESSOR_CONTACT_PERSON = 4; + private static final int RELEASE_DT_ROW_ASSESSOR_DEPARTMENT = 5; + private static final int RELEASE_DT_ROW_ASSESSMENT_DATE = 6; @Override public void doView(RenderRequest request, RenderResponse response) throws IOException, PortletException { @@ -59,19 +88,175 @@ public void doView(RenderRequest request, RenderResponse response) throws IOExce } private void prepareStandardView(RenderRequest request) { - final User user = UserCacheHolder.getUserFromRequest(request); - ComponentService.Iface client = thriftClients.makeComponentClient(); - try { - final List releaseSummary = client.getAccessibleReleaseSummary(user); + request.setAttribute(PortalConstants.EXACT_MATCH_CHECKBOX, nullToEmpty(request.getParameter(PortalConstants.EXACT_MATCH_CHECKBOX))); + } catch (Exception e) { + log.error("Could not fetch releases from backend", e); + } - request.setAttribute(RELEASE_LIST, releaseSummary); + } + + @Override + public void serveResource(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + try { + String action = request.getParameter(PortalConstants.ACTION); - } catch (TException e) { + if (PortalConstants.LOAD_ECC_LIST.equals(action)) { + serveECCReleaseList(request, response); + } + } catch (Exception e) { log.error("Could not fetch releases from backend", e); request.setAttribute(RELEASE_LIST, Collections.emptyList()); } + + + } + + private void serveECCReleaseList(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + HttpServletRequest originalServletRequest = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(request)); + PaginationParameters paginationParameters = PaginationParser.parametersFrom(originalServletRequest); + //PortletUtils.handlePaginationSortOrder(request, paginationParameters, releaseFilteredFields, RELEASE_NO_SORT); + PaginationData pageData = new PaginationData(); + pageData.setRowsPerPage(paginationParameters.getDisplayLength()); + pageData.setDisplayStart(paginationParameters.getDisplayStart()); + pageData.setAscending(paginationParameters.isAscending().get()); + int sortParam = -1; + if (paginationParameters.getSortingColumn().isPresent()) { + sortParam = paginationParameters.getSortingColumn().get(); + if (sortParam == 1 && Integer.valueOf(paginationParameters.getEcho()) == 1) { + pageData.setSortColumnNumber(-1); + } else { + pageData.setSortColumnNumber(sortParam); + } + } + Map> releaseList = getReleaseList(request, pageData); + JSONArray jsonReleases = getReleaseData(releaseList.values().iterator().next(), paginationParameters, request); + JSONObject jsonResult = createJSONObject(); + int count = (int) (releaseList.keySet().iterator().next().getTotalRowCount()); + jsonResult.put(DATATABLE_RECORDS_TOTAL, count); + jsonResult.put(DATATABLE_RECORDS_FILTERED, count); + jsonResult.put(DATATABLE_DISPLAY_DATA, jsonReleases); + + try { + writeJSON(request, response, jsonResult); + } catch (IOException e) { + log.error("Problem rendering RequestStatus", e); + } + } + + private Map> getReleaseList(PortletRequest request, PaginationData pageData) throws IOException { + final User user = UserCacheHolder.getUserFromRequest(request); + ComponentService.Iface client = thriftClients.makeComponentClient(); + Map> releaseSummary = Maps.newHashMap(); + try { + releaseSummary = client.getAccessibleReleasesWithPagination(user, pageData); + request.setAttribute(RELEASE_LIST, releaseSummary); + } + catch (TException e) { + log.error("Could not search releases in backend ", e); + } + return releaseSummary; + } + + public JSONArray getReleaseData(List releaseList, PaginationParameters releaseParameters, ResourceRequest request) { + List sortedReleases = sortReleaseList(releaseList, releaseParameters); + int count = getReleaseDataCount(releaseParameters, releaseList.size()); + final int start = 0; + JSONArray releaseData = createJSONArray(); + for (int i = start; i < count; i++) { + JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); + Release release = sortedReleases.get(i); + jsonObject.put("id", release.getId()); + jsonObject.put("DT_RowId", release.getId()); + jsonObject.put("status", nullToEmptyString(release.getEccInformation().getEccStatus())); + jsonObject.put("name", SW360Utils.printName(release)); + jsonObject.put("version", nullToEmpty(release.getVersion())); + jsonObject.put("group", nullToEmptyString(release.getCreatorDepartment())); + jsonObject.put("assessor_contact_person", nullToEmptyString(release.getEccInformation().getAssessorContactPerson())); + jsonObject.put("assessor_dept", nullToEmptyString(release.getEccInformation().getAssessorDepartment())); + jsonObject.put("assessment_date", nullToEmptyString(release.getEccInformation().getAssessmentDate())); + releaseData.put(jsonObject); + } + return releaseData; + } + + public static int getReleaseDataCount(PaginationParameters releaseParameters, int maxSize) { + if (releaseParameters.getDisplayLength() == -1) { + return maxSize; + } else { + return min(releaseParameters.getDisplayStart() + releaseParameters.getDisplayLength(), maxSize); + } + } + + private List sortReleaseList(List releaseList, PaginationParameters releaseParameters) { + boolean isAsc = releaseParameters.isAscending().orElse(true); + + switch (releaseParameters.getSortingColumn().orElse(RELEASE_DT_ROW_ECC_STATUS)) { + case RELEASE_DT_ROW_ECC_STATUS: + Collections.sort(releaseList, compareByECCStatus(isAsc)); + break; + case RELEASE_DT_ROW_NAME: + Collections.sort(releaseList, compareByName(isAsc)); + break; + case RELEASE_DT_ROW_VERSION: + Collections.sort(releaseList, compareByVersion(isAsc)); + break; + case RELEASE_DT_ROW_GROUP: + Collections.sort(releaseList, compareByCreatorGroup(isAsc)); + break; + case RELEASE_DT_ROW_ASSESSOR_CONTACT_PERSON: + Collections.sort(releaseList, compareByAssessorContactPerson(isAsc)); + break; + case RELEASE_DT_ROW_ASSESSOR_DEPARTMENT: + Collections.sort(releaseList, compareByAssessorDept(isAsc)); + break; + case RELEASE_DT_ROW_ASSESSMENT_DATE: + Collections.sort(releaseList, compareByAssessmentDate(isAsc)); + break; + default: + break; + } + return releaseList; } + + public static Comparator compareByECCStatus(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getEccInformation().getEccStatus())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByName(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getName())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByVersion(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getVersion())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByCreatorGroup(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getCreatorDepartment())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByAssessorContactPerson(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getEccInformation().getAssessorContactPerson())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByAssessorDept(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getEccInformation().getAssessorDepartment())); + return isAscending ? comparator : comparator.reversed(); + } + + public static Comparator compareByAssessmentDate(boolean isAscending) { + Comparator comparator = Comparator.comparing(r -> CommonUtils.nullToEmptyString(r.getEccInformation().getAssessmentDate())); + return isAscending ? comparator : comparator.reversed(); + } + + + + } diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/ecc/view.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/ecc/view.jsp index c43358844c..b1481868d2 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/ecc/view.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/ecc/view.jsp @@ -16,7 +16,6 @@ - @@ -26,6 +25,10 @@ + + + +
@@ -44,39 +47,6 @@
- - - - - - - - - - - - - - - - - - - - - - - -
- - class="badge badge-empty badge-danger" <%--ECCStatus.OPEN || ECCStatus.REJECTED--%> - class="badge badge-empty badge-warning" <%--ECCStatus.IN_PROGRESS--%> - class="badge badge-empty badge-success" > <%--ECCStatus.APPROVED--%> - ! <%--ECCStatus.REJECTED--%> -   <%--ECCStatus.REJECTED--%> - -
@@ -90,7 +60,7 @@ AUI().use('liferay-portlet-url', function () { var PortletURL = Liferay.PortletURL; - require(['jquery', 'utils/includes/quickfilter', 'bridges/datatables'], function($, quickfilter, datatables) { + require(['jquery', 'utils/includes/quickfilter', 'bridges/datatables', 'bridges/jquery-ui'], function($, quickfilter, datatables) { var eccInfoTable; // initializing @@ -107,6 +77,18 @@ function configureEccInfoTable(){ return datatables.create('#eccInfoTable', { + bServerSide: true, + sAjaxSource: '<%=loadECCURL%>', + columns: [ + {"title": "", data: "status"}, + {"title": "", data: function(row){return row["name"];}, render: {display: renderReleaseNameLink}}, + {"title": "", data: "version", render: {display: renderReleaseNameLink}}, + {"title": "", data: "group"}, + {"title": "", data: "assessor_contact_person"}, + {"title": "", data: "assessor_dept"}, + {"title": "", data: "assessment_date"}, + ], + columnDefs: [], language: { url: "", loadingRecords: "" @@ -114,6 +96,9 @@ searching: true }, [0, 1, 2, 3, 4, 5, 6]); } + function renderReleaseNameLink(name, type, row) { + return $("").text(name)[0].outerHTML; + } }); }); diff --git a/libraries/datahandler/src/main/thrift/components.thrift b/libraries/datahandler/src/main/thrift/components.thrift index e411f5886b..ed264a1a0c 100644 --- a/libraries/datahandler/src/main/thrift/components.thrift +++ b/libraries/datahandler/src/main/thrift/components.thrift @@ -473,6 +473,11 @@ service ComponentService { */ list searchAccessibleReleases(1: string text, 2: User user); + /** + * list accessible releases with pagination for ECC page + */ + map> getAccessibleReleasesWithPagination(1: User user, 2: PaginationData pageData); + /** * get short summary of release by release name prefix **/ From c3492c3223e462341ac6aa8f90a409f50b1a899b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 22:57:37 +0000 Subject: [PATCH 06/17] chore(deps): bump guava from 31.1-jre to 32.0.0-jre Bumps [guava](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a482cff771..81b39026e1 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,7 @@ 1.0.1 1.3.9-1 2.3.2 - 31.1-jre + 32.0.0-jre 1.3 4.5.14 4.4.16 From 57b02aa2918cb1ba6b8385efa5b62bccd2d72d85 Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Tue, 23 May 2023 17:47:25 +0700 Subject: [PATCH 07/17] feat(REST): Update response endpoint get attachments by release Signed-off-by: tuannn2 --- .../release/ReleaseController.java | 6 +- .../restdocs/ReleaseSpecTest.java | 55 ++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java index 37fd6ee78d..474bdd6ec2 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java @@ -25,6 +25,8 @@ import org.eclipse.sw360.datahandler.resourcelists.ResourceClassNotFoundException; import org.eclipse.sw360.datahandler.thrift.*; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; +import org.eclipse.sw360.datahandler.thrift.attachments.UsageAttachment; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.components.Component; @@ -413,11 +415,11 @@ public ResponseEntity> createRelease( } @GetMapping(value = RELEASES_URL + "/{id}/attachments") - public ResponseEntity>> getReleaseAttachments( + public ResponseEntity>> getReleaseAttachment1s( @PathVariable("id") String id) throws TException { final User sw360User = restControllerHelper.getSw360UserFromAuthentication(); final Release sw360Release = releaseService.getReleaseForUserById(id, sw360User); - final CollectionModel> resources = attachmentService.getResourcesFromList(sw360Release.getAttachments()); + final CollectionModel> resources = attachmentService.getAttachmentDTOResourcesFromList(sw360User, sw360Release.getAttachments(), Source.releaseId(sw360Release.getId())); return new ResponseEntity<>(resources, HttpStatus.OK); } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index 83aa873e10..b87979bc29 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -13,12 +13,9 @@ import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.thrift.*; -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.vulnerabilities.*; import org.eclipse.sw360.rest.resourceserver.attachment.AttachmentInfo; -import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; -import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentContent; import org.eclipse.sw360.datahandler.thrift.components.COTSDetails; import org.eclipse.sw360.datahandler.thrift.components.ClearingInformation; import org.eclipse.sw360.datahandler.thrift.components.ClearingState; @@ -247,6 +244,36 @@ public void before() throws TException, IOException { attachment3.setAttachmentType(AttachmentType.SOURCE); release3.setAttachments(ImmutableSet.of(attachment3)); + AttachmentDTO attachmentDTO = new AttachmentDTO(); + attachmentDTO.setAttachmentContentId(attachment3.getAttachmentContentId()); + attachmentDTO.setFilename(attachment3.getFilename()); + attachmentDTO.setSha1(attachment3.getSha1()); + attachmentDTO.setAttachmentType(attachment3.getAttachmentType()); + attachmentDTO.setCreatedBy(attachment3.getCreatedBy()); + attachmentDTO.setCreatedTeam(attachment3.getCreatedTeam()); + attachmentDTO.setCreatedComment(attachment3.getCreatedComment()); + attachmentDTO.setCreatedOn(attachment3.getCreatedOn()); + attachmentDTO.setCheckedBy(attachment3.getCheckedBy()); + attachmentDTO.setCheckedTeam(attachment3.getCheckedTeam()); + attachmentDTO.setCheckedComment(attachment3.getCheckedComment()); + attachmentDTO.setCheckedOn(attachment3.getCheckedOn()); + attachmentDTO.setCheckStatus(attachment3.getCheckStatus()); + + UsageAttachment usageAttachment = new UsageAttachment(); + usageAttachment.setVisible(1); + usageAttachment.setRestricted(0); + + ProjectUsage projectUsage = new ProjectUsage(); + projectUsage.setProjectId("376576"); + projectUsage.setProjectName("Emerald Web"); + + usageAttachment.setProjectUsages(new HashSet<>(Arrays.asList(projectUsage))); + + attachmentDTO.setUsageAttachment(usageAttachment); + List> atEntityModels = new ArrayList<>(); + atEntityModels.add(EntityModel.of(attachmentDTO)); + given(this.attachmentServiceMock.getAttachmentDTOResourcesFromList(any(), any(), any())).willReturn(CollectionModel.of(atEntityModels)); + Set projectList = new HashSet<>(); project = new Project(); project.setId("376576"); @@ -620,15 +647,27 @@ public void should_document_update_release() throws Exception { @Test public void should_document_get_release_attachment_info() throws Exception { String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); - mockMvc.perform(get("/api/releases/" + release.getId() + "/attachments") + mockMvc.perform(get("/api/releases/" + release3.getId() + "/attachments") .header("Authorization", "Bearer " + accessToken) .accept(MediaTypes.HAL_JSON)) .andExpect(status().isOk()) .andDo(this.documentationHandler.document( responseFields( - subsectionWithPath("_embedded.sw360:attachments").description("An array of <>"), - subsectionWithPath("_embedded.sw360:attachments.[]filename").description("The attachment filename"), - subsectionWithPath("_embedded.sw360:attachments.[]sha1").description("The attachment sha1 value"), + subsectionWithPath("_embedded.sw360:attachmentDTOes").description("An array of <>"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]attachmentContentId").description("The attachment attachmentContentId"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]filename").description("The attachment filename"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]sha1").description("The attachment sha1"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]attachmentType").description("The attachment attachmentType"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdBy").description("The attachment createdBy"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdTeam").description("The attachment createdTeam"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdComment").description("The attachment createdComment"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]createdOn").description("The attachment createdOn"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]checkedComment").description("The attachment checkedComment"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]checkStatus").description("The attachment checkStatus"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment").description("The usages in project"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment.visible").description("The visible usages in project"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment.restricted").description("The restricted usages in project"), + subsectionWithPath("_embedded.sw360:attachmentDTOes.[]usageAttachment.projectUsages").description("The name of project usages"), subsectionWithPath("_links").description("<> to other resources") ))); } From b02f90ec23ca7fa5c1143052d0737e8e03ee9a60 Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Mon, 19 Jun 2023 10:06:48 +0700 Subject: [PATCH 08/17] fix(update): Update the migration readme file Signed-off-by: tuannn2 --- scripts/migrations/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/migrations/README.md b/scripts/migrations/README.md index a830042437..718e470435 100644 --- a/scripts/migrations/README.md +++ b/scripts/migrations/README.md @@ -4,10 +4,12 @@ This folder contains scripts which implement the database migrations. The scripts are written in `python2` and depend on `python2-couchdb`. The couchdb has to run and be accessible on `localhost:5984`. +From release 16.0.0 onwards python2 is not supported.All migration scripts form release 16.0.0 onwards are in python3. + To migrate it is recommended to do this in the following order: 1. stop SW360 (i.e. the tomcat) 2. ensure that couchdb is accessible (try to open `http://localhost:5984/_utils/`) -3. run the migration scripts (i.e. for each script call `python2 /PATH/TO/00?_some_migration_script.py`) +3. run the migration scripts (i.e. for each script call `python3 /PATH/TO/00?_some_migration_script.py`) * be aware that some scripts are using an internal dry-run switch which you have to change manually in the script's code 4. deploy the new `.war` files 5. start SW360 again From 81b6ca3a790e30c27d521a8d68d25ba99eb1fefc Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Tue, 20 Jun 2023 14:27:11 +0700 Subject: [PATCH 09/17] fix(readme): Add Information of python2 to python3 change when running file migration scripts Signed-off-by: tuannn2 --- scripts/migrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/migrations/README.md b/scripts/migrations/README.md index 718e470435..9692a48a1e 100644 --- a/scripts/migrations/README.md +++ b/scripts/migrations/README.md @@ -4,7 +4,7 @@ This folder contains scripts which implement the database migrations. The scripts are written in `python2` and depend on `python2-couchdb`. The couchdb has to run and be accessible on `localhost:5984`. -From release 16.0.0 onwards python2 is not supported.All migration scripts form release 16.0.0 onwards are in python3. +From release 16.0.0 onwards python2 is not supported.All migration scripts form release 16.0.0 onwards are in python3. To adapt the migrate scripts to be Python 3 compatible, you need to change the print statement to a print() function. Because in Python 2, print is a statement and can be used without parentheses. However, in Python 3, print is a function and therefore always requires parentheses. Try modifying print "CR Id: " + cr.get("_id") like this: print("CR Id: " + str(cr.get("_id"))). To migrate it is recommended to do this in the following order: 1. stop SW360 (i.e. the tomcat) From 9d79b28969d096a4b01859e0316f7aa29bea917d Mon Sep 17 00:00:00 2001 From: Nikesh kumar Date: Wed, 21 Jun 2023 20:41:45 +0530 Subject: [PATCH 10/17] fix(rest): Added endpoint url for summary and administration page info Signed-off-by: Nikesh kumar --- .../src/docs/asciidoc/projects.adoc | 14 +++++ .../core/JacksonCustomizations.java | 3 - .../project/ProjectController.java | 18 +++++- .../restdocs/ProjectSpecTest.java | 63 +++++++++++++++++++ 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/projects.adoc b/rest/resource-server/src/docs/asciidoc/projects.adoc index 000ca7f2e2..fcbfe3d862 100644 --- a/rest/resource-server/src/docs/asciidoc/projects.adoc +++ b/rest/resource-server/src/docs/asciidoc/projects.adoc @@ -664,3 +664,17 @@ include::{snippets}/should_document_get_project_count/response-fields.adoc[] ===== Example response include::{snippets}/should_document_get_project_count/http-response.adoc[] + +[[resources-project-get-summaryadministraion-project]] +==== Administration and Summary Info + +A `GET` request will get summary and administration page of project tab. + +===== Response structure +include::{snippets}/should_document_create_summary_administration/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_create_summary_administration/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_create_summary_administration/http-response.adoc[] \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 2a3fac6649..fc076ff47f 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -198,14 +198,11 @@ static abstract class MultiStatusMixin extends MultiStatus { "setConsiderReleasesFromExternalList", "externalUrlsSize", "setExternalUrls", - "externalUrls", "setVendor", "setVendorId", "setSpdxId", "setModifiedOn", - "modifiedOn", "setModifiedBy", - "modifiedBy" }) static abstract class ProjectMixin extends Project { diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/project/ProjectController.java index f2048fcf94..02f7128686 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 @@ -1106,7 +1106,7 @@ private HalResource createHalProject(Project sw360Project, User sw360Us if (sw360Project.getVendor() != null) { Vendor vendor = sw360Project.getVendor(); - HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor.getFullname()); + Vendor vendorHalResource = restControllerHelper.convertToEmbeddedVendor(vendor); halProject.addEmbeddedResource("sw360:vendors", vendorHalResource); sw360Project.setVendor(null); } @@ -1227,4 +1227,20 @@ public void getUserProjectCount(HttpServletResponse response) throws TException throw new SW360Exception(e.getMessage()); } } + @RequestMapping(value = PROJECTS_URL + "/{id}/summaryAdministration", method = RequestMethod.GET) + public ResponseEntity> getAdministration( + @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); + HalResource userHalResource = createHalProject(sw360Project, sw360User); + sw360Project.unsetLinkedProjects(); + sw360Project.unsetReleaseIdToUsage(); + + return new ResponseEntity<>(userHalResource, HttpStatus.OK); + } + } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java index 87aff92c57..5bc4ef90b3 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ProjectSpecTest.java @@ -218,6 +218,7 @@ public void before() throws TException, IOException { project.setSpecialRisksOSS("Lorem Ipsum"); project.setGeneralRisks3rdParty("Lorem Ipsum"); project.setSpecialRisks3rdParty("Lorem Ipsum"); + project.setLicenseInfoHeaderText("Lorem Ipsum"); project.setDeliveryChannels("Lorem Ipsum"); project.setVendor(new Vendor()); project.setRemarksAdditionalRequirements("Lorem Ipsum"); @@ -232,6 +233,10 @@ public void before() throws TException, IOException { project.setAdditionalData(additionalData); project.setPhaseOutSince("2020-06-24"); project.setClearingRequestId("CR-1"); + Map externalURLs1 = new HashMap<>(); + externalURLs1.put("homepage", "http://test_wiki_url.com"); + externalURLs1.put("wiki", "http://test_wiki_url.com"); + project.setExternalUrls(externalURLs1); projectListByName.add(project); projectList.add(project); @@ -266,6 +271,7 @@ public void before() throws TException, IOException { project2.setSpecialRisks3rdParty("Lorem Ipsum"); project2.setDeliveryChannels("Lorem Ipsum"); project2.setRemarksAdditionalRequirements("Lorem Ipsum"); + project2.setLicenseInfoHeaderText("Lorem Ipsum"); project2.setVendor(new Vendor()); project2.setSecurityResponsibles(new HashSet<>(Arrays.asList("securityresponsible1@sw360.org", "securityresponsible2@sw360.org"))); project2.setProjectResponsible("projectresponsible@sw360.org"); @@ -678,6 +684,8 @@ public void should_document_get_projects_with_all_details() throws Exception { subsectionWithPath("_embedded.sw360:projects.[]_embedded.clearingTeam").description("The clearingTeam of the project").optional(), subsectionWithPath("_embedded.sw360:projects.[]_embedded.homepage").description("The homepage url of the project").optional(), subsectionWithPath("_embedded.sw360:projects.[]_embedded.wiki").description("The wiki url of the project").optional(), + subsectionWithPath("_embedded.sw360:projects.[]licenseInfoHeaderText").description("The licenseInfoHeaderText text of the project"), + subsectionWithPath("_embedded.sw360:projects.[]externalUrls").description("A place to store additional data used by external tools").optional(), subsectionWithPath("_embedded.sw360:projects.[]_embedded.sw360:moderators").description("An array of all project moderators with email").optional(), subsectionWithPath("_embedded.sw360:projects.[]_embedded.sw360:contributors").description("An array of all project contributors with email").optional(), subsectionWithPath("_embedded.sw360:projects.[]_embedded.sw360:attachments").description("An array of all project attachments").optional(), @@ -737,6 +745,8 @@ public void should_document_get_project() throws Exception { fieldWithPath("enableVulnerabilitiesDisplay").description("Displaying vulnerabilities flag."), fieldWithPath("state").description("The project active status, possible values are: " + Arrays.asList(ProjectState.values())), fieldWithPath("phaseOutSince").description("The project phase-out date"), + fieldWithPath("licenseInfoHeaderText").description("The licenseInfoHeaderText text of the project"), + subsectionWithPath("externalUrls").description("A place to store additional data used by external URLs"), fieldWithPath("clearingRequestId").description("Clearing Request id associated with project."), subsectionWithPath("_links").description("<> to other resources"), subsectionWithPath("_embedded.createdBy").description("The user who created this project"), @@ -1438,6 +1448,8 @@ public void should_document_update_project() throws Exception { subsectionWithPath("_links").description("<> to other resources"), subsectionWithPath("_embedded.createdBy").description("The user who created this project"), fieldWithPath("enableSvm").description("Security vulnerability monitoring flag"), + fieldWithPath("licenseInfoHeaderText").description("The licenseInfoHeaderText text of the project"), + subsectionWithPath("externalUrls").description("A place to store additional data used by external URLs"), fieldWithPath("considerReleasesFromExternalList").description("Consider list of releases from existing external list"), fieldWithPath("enableVulnerabilitiesDisplay").description("Displaying vulnerabilities flag."), subsectionWithPath("_embedded.sw360:moderators").description("An array of moderators"), @@ -1681,4 +1693,55 @@ public void should_document_get_project_count() throws Exception { fieldWithPath("count").description("Count of projects for a user.").optional() ))); } + @Test + public void should_document_create_summary_administration() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/projects/" + project.getId()+ "/summaryAdministration") + .header("Authorization", "Bearer " + accessToken) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + responseFields( + fieldWithPath("name").description("The name of the project"), + fieldWithPath("version").description("The project version"), + fieldWithPath("createdOn").description("The date the project was created"), + fieldWithPath("projectType").description("The project type, possible values are: " + Arrays.asList(ProjectType.values())), + fieldWithPath("domain").description("The domain, possible values are:" + Sw360ResourceServer.DOMAIN.toString()), + fieldWithPath("visibility").description("The project visibility, possible values are: " + Arrays.asList(Visibility.values())), + subsectionWithPath("externalIds").description("When projects are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), + subsectionWithPath("additionalData").description("A place to store additional data used by external tools"), + fieldWithPath("ownerAccountingUnit").description("The owner accounting unit of the project"), + fieldWithPath("ownerGroup").description("The owner group of the project"), + fieldWithPath("description").description("The project description"), + fieldWithPath("ownerCountry").description("The owner country of the project"), + fieldWithPath("obligationsText").description("The obligations text of the project"), + fieldWithPath("clearingSummary").description("The clearing summary text of the project"), + fieldWithPath("specialRisksOSS").description("The special risks OSS text of the project"), + fieldWithPath("generalRisks3rdParty").description("The general risks 3rd party text of the project"), + fieldWithPath("specialRisks3rdParty").description("The special risks 3rd party text of the project"), + fieldWithPath("deliveryChannels").description("The sales and delivery channels text of the project"), + fieldWithPath("remarksAdditionalRequirements").description("The remark additional requirements text of the project"), + fieldWithPath("tag").description("The project tag"), + fieldWithPath("businessUnit").description("The business unit this project belongs to"), + fieldWithPath("deliveryStart").description("The project delivery start date"), + fieldWithPath("preevaluationDeadline").description("The project preevaluation deadline"), + fieldWithPath("systemTestStart").description("Date of the project system begin phase"), + fieldWithPath("systemTestEnd").description("Date of the project system end phase"), + fieldWithPath("securityResponsibles").description("An array of users responsible for security of the project."), + fieldWithPath("projectResponsible").description("A user who is responsible for the project."), + fieldWithPath("enableSvm").description("Security vulnerability monitoring flag"), + fieldWithPath("considerReleasesFromExternalList").description("Consider list of releases from existing external list"), + fieldWithPath("enableVulnerabilitiesDisplay").description("Displaying vulnerabilities flag."), + fieldWithPath("state").description("The project active status, possible values are: " + Arrays.asList(ProjectState.values())), + fieldWithPath("phaseOutSince").description("The project phase-out date"), + fieldWithPath("clearingRequestId").description("Clearing Request id associated with project."), + fieldWithPath("licenseInfoHeaderText").description("Display licenseInfoHeaderText info"), + subsectionWithPath("externalUrls").description("A place to store additional data used by external URLs"), + subsectionWithPath("_embedded.createdBy").description("The user who created this project"), + subsectionWithPath("_embedded.sw360:moderators").description("An array of moderators"), + subsectionWithPath("_embedded.sw360:vendors").description("An array of all component vendors with full name and link to their <>"), + subsectionWithPath("_embedded.sw360:attachments").description("An array of all project attachments and link to their <>"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } } From 04c64a580d479d9e4e397e22f6b218ca8ae188ca Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Tue, 27 Jun 2023 10:26:28 +0700 Subject: [PATCH 11/17] fix(rest): Missing moderators field when creating component using API Signed-off-by: tuannn2 --- .../sw360/rest/resourceserver/core/JacksonCustomizations.java | 1 - .../sw360/rest/resourceserver/restdocs/ComponentSpecTest.java | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index fc076ff47f..b3663fe3aa 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -320,7 +320,6 @@ static abstract class UserMixin extends User { "revision", "attachments", "createdBy", - "moderators", "releases", "wikipedia", "openHub", diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java index 11d57402c9..94cb33b7ba 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java @@ -544,6 +544,7 @@ public void should_document_get_components_with_all_details() throws Exception { subsectionWithPath("_embedded.sw360:components.[]modifiedOn").description("The date the component was modified"), subsectionWithPath("_embedded.sw360:components.[]categories").description("The component categories"), + subsectionWithPath("_embedded.sw360:components.[]moderators").description("The component moderators"), subsectionWithPath("_embedded.sw360:components.[]languages").description("The language of the component"), subsectionWithPath("_embedded.sw360:components.[]operatingSystems").description("The OS on which the component operates"), @@ -712,6 +713,7 @@ public void should_document_get_component() throws Exception { fieldWithPath("ownerGroup").description("The owner group of the component"), fieldWithPath("ownerCountry").description("The owner country of the component"), fieldWithPath("categories").description("The component categories"), + fieldWithPath("moderators").description("The component moderators"), fieldWithPath("languages").description("The language of the component"), subsectionWithPath("externalIds").description("When components are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), subsectionWithPath("additionalData").description("A place to store additional data used by external tools"), @@ -1011,6 +1013,7 @@ private RestDocumentationResultHandler documentComponentProperties() { fieldWithPath("wiki").description("The wiki of component"), fieldWithPath("blog").description("The blog of component"), fieldWithPath("categories").description("The component categories"), + fieldWithPath("moderators").description("The component moderators"), fieldWithPath("languages").description("The language of the component"), fieldWithPath("mailinglist").description("Component mailing lists"), fieldWithPath("operatingSystems").description("The OS on which the component operates"), From e8f6e6b263a321030c3a7093ebe2d9e337b4390b Mon Sep 17 00:00:00 2001 From: tuannn2 Date: Thu, 1 Jun 2023 16:35:41 +0700 Subject: [PATCH 12/17] feat(rest): update response API Get a single release Signed-off-by: tuannn2 --- .../component/Sw360ComponentService.java | 4 +- .../core/JacksonCustomizations.java | 14 +-- .../core/RestControllerHelper.java | 101 +++++++++++++++++- .../release/ReleaseController.java | 14 ++- .../release/Sw360ReleaseService.java | 31 +++--- .../restdocs/ReleaseSpecTest.java | 36 +++++-- 6 files changed, 155 insertions(+), 45 deletions(-) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java index b13a841e30..1749456ed3 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/component/Sw360ComponentService.java @@ -193,7 +193,7 @@ public List convertReleaseToReleaseLink(String id,User user) throws ClearingReport clearingReport = new ClearingReport(); Set attachments = getAttachmentForClearingReport(release); - if (attachments.size() != 0 ) { + if (!attachments.equals(Collections.emptySet())) { Set attachmentsAccepted = getAttachmentsStatusAccept(attachments); if(attachmentsAccepted.size() != 0) { clearingReport.setClearingReportStatus(ClearingReportStatus.DOWNLOAD); @@ -217,6 +217,8 @@ public List convertReleaseToReleaseLink(String id,User user) throws private Set getAttachmentForClearingReport(Release release){ final Set attachments = release.getAttachments(); + if (CommonUtils.isNullOrEmptyCollection(attachments)) + return Collections.emptySet(); return attachments.stream().filter(attachment -> AttachmentType.COMPONENT_LICENSE_INFO_XML.equals(attachment.getAttachmentType()) || AttachmentType.CLEARING_REPORT.equals(attachment.getAttachmentType())) .collect(Collectors.toSet()); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index fc076ff47f..ee7b59975a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -404,6 +404,8 @@ static abstract class ComponentMixin extends Component { "revision", "permissions", "moderators", + "subscribers", + "contributors", "clearingInformation", "setAttachments", "setCreatedOn", @@ -471,7 +473,6 @@ static abstract class ComponentMixin extends Component { "otherLicenseIdsSize", "setOtherLicenseIds", "setModifiedOn", - "modifiedOn", "setModifiedBy", "modifiedBy", "setComponentType" @@ -488,18 +489,12 @@ static abstract class ReleaseMixin extends Release { @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties({ - "vendor", - "longName", - "releaseRelationship", - "hasSubreleases", "nodeId", "parentNodeId", "componentType", - "licenseIds", "licenseNames", "comment", "otherLicenseIds", - "accessible", "attachmentsSize", "setName", "setVersion", @@ -997,11 +992,6 @@ public static abstract class VulnerabilityApiDTOMixin extends VulnerabilityApiDT @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonIgnoreProperties({ - "assessorContactPerson", - "assessorDepartment", - "eccComment", - "materialIndexNumber", - "assessmentDate", "setEccComment", "setEccn", "setEccStatus", 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 034274f05b..71a11988f8 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 @@ -36,6 +36,7 @@ import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.components.ReleaseLink; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.licenses.Obligation; import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest; @@ -100,6 +101,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -293,6 +295,56 @@ public void addEmbeddedContributors(HalResource halResource, Set contrib } } + public void addEmbeddedDataToHalResourceRelease(HalResource halResource, Release sw360Release) { + addEmbeddedContributorsToHalResourceRelease(halResource, sw360Release); + addEmbeddedCreatedByToHalResourceRelease(halResource, sw360Release.getCreatedBy()); + addEmbeddedModifiedByToHalResourceRelease(halResource, sw360Release.getModifiedBy()); + addEmbeddedSubcribeToHalResourceRelease(halResource, sw360Release); + } + + public void addEmbeddedContributorsToHalResourceRelease(HalResource halResource, Release sw360Release) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Release.getContributors())) { + Set contributors = sw360Release.getContributors(); + for (String contributorEmail : contributors) { + User sw360User = getUserByEmail(contributorEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:contributors"); + sw360Release.setContributors(null); + } + + } + } + } + + public void addEmbeddedSubcribeToHalResourceRelease(HalResource halResource, Release sw360Release) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Release.getSubscribers())) { + Set subscribers = sw360Release.getSubscribers(); + for (String subscribersEmail : subscribers) { + User sw360User = getUserByEmail(subscribersEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:subscribers"); + sw360Release.setSubscribers(null); + } + } + } + } + + public void addEmbeddedCreatedByToHalResourceRelease(HalResource halRelease, String createdBy) { + if (CommonUtils.isNotNullEmptyOrWhitespace(createdBy)) { + User releaseCreator = getUserByEmail(createdBy); + if (null != releaseCreator ) + addEmbeddedUser(halRelease, releaseCreator, "sw360:createdBy"); + } + } + + public void addEmbeddedModifiedByToHalResourceRelease(HalResource halRelease, String modifiedBy) { + if (CommonUtils.isNotNullEmptyOrWhitespace(modifiedBy)) { + User releaseModify = getUserByEmail(modifiedBy); + if (null != releaseModify) + addEmbeddedUser(halRelease, releaseModify, "sw360:modifiedBy"); + } + } + public void addEmbeddedLeadArchitect(HalResource halResource, String leadArchitect) { User sw360User = getUserByEmail(leadArchitect); addEmbeddedUser(halResource, sw360User, "leadArchitect"); @@ -317,6 +369,15 @@ public void addEmbeddedReleases( } } + public void addEmbeddedReleaseLinks( + HalResource halResource, + List releaseLinks) { + List releaseLinkInogreAttachments = releaseLinks.stream().map(releaseLink -> releaseLink.setAttachments(null)).collect(Collectors.toList()); + for (ReleaseLink releaseLink : releaseLinkInogreAttachments) { + addEmbeddedReleaseLink(halResource, releaseLink); + } + } + public void addEmbeddedUser(HalResource halResource, User user, String relation) { User embeddedUser = convertToEmbeddedUser(user); EntityModel embeddedUserResource = EntityModel.of(embeddedUser); @@ -353,6 +414,23 @@ public HalResource addEmbeddedVendor(String vendorFullName) { return null; } + public HalResource addEmbeddedVendor(Vendor vendor) { + Vendor embeddedVendor = convertToEmbeddedVendor(vendor); + HalResource halVendor = new HalResource<>(embeddedVendor); + try { + Vendor vendorByFullName = vendorService.getVendorByFullName(vendor.getFullname()); + if(vendorByFullName != null) { + Link vendorSelfLink = linkTo(UserController.class) + .slash("api" + VendorController.VENDORS_URL + "/" + vendorByFullName.getId()).withSelfRel(); + halVendor.add(vendorSelfLink); + } + return halVendor; + } catch (Exception e) { + LOGGER.error("cannot create self link for vendor with full name: " + vendor.getFullname()); + } + return null; + } + public void addEmbeddedLicenses(HalResource halComponent, Set licenseIds) { for (String licenseId : licenseIds) { HalResource licenseHalResource = addEmbeddedLicense(licenseId); @@ -395,6 +473,11 @@ public void addEmbeddedRelease(HalResource halResource, Release release) { halResource.addEmbeddedResource("sw360:releases", halRelease); } + public void addEmbeddedReleaseLink(HalResource halResource, ReleaseLink releaseLink) { + HalResource halRelease = new HalResource<>(releaseLink); + halResource.addEmbeddedResource("sw360:releaseLinks", halRelease); + } + public void addEmbeddedAttachments( HalResource halResource, Set attachments) { @@ -652,9 +735,10 @@ public Obligation convertToEmbeddedObligation(Obligation obligation) { } public Vendor convertToEmbeddedVendor(Vendor vendor) { - Vendor embeddedVendor = convertToEmbeddedVendor(vendor.getFullname()); + Vendor embeddedVendor = new Vendor(); embeddedVendor.setId(vendor.getId()); embeddedVendor.setShortname(vendor.getShortname()); + embeddedVendor.setFullname(vendor.getFullname()); embeddedVendor.setUrl(vendor.getUrl()); return embeddedVendor; } @@ -988,6 +1072,7 @@ public void addEmbeddedModerationRequest(HalResource halResource, ModerationRequ public void addEmbeddedDataToComponent(HalResource halResource, Component sw360Component) { addEmbeddedModifiedByToComponent(halResource,sw360Component); addEmbeddedComponentOwnerToComponent(halResource,sw360Component); + addEmbeddedSubcribeToHalResourceComponent(halResource,sw360Component); } public void addEmbeddedModifiedByToComponent(HalResource halResource, Component sw360Component) { @@ -1007,4 +1092,18 @@ public void addEmbeddedComponentOwnerToComponent(HalResource halResource, Compon } } } + + public void addEmbeddedSubcribeToHalResourceComponent(HalResource halResource, Component sw360Component) { + if (!CommonUtils.isNullOrEmptyCollection(sw360Component.getSubscribers())) { + Set subscribers = sw360Component.getSubscribers(); + for (String subscribersEmail : subscribers) { + User sw360User = getUserByEmail(subscribersEmail); + if (null != sw360User) { + addEmbeddedUser(halResource, sw360User, "sw360:subscribers"); + sw360Component.setSubscribers(null); + } + } + } + } + } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java index 474bdd6ec2..54633a4e0b 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/ReleaseController.java @@ -28,6 +28,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentDTO; import org.eclipse.sw360.datahandler.thrift.attachments.UsageAttachment; 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.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; @@ -173,13 +174,10 @@ public ResponseEntity getRelease( User sw360User = restControllerHelper.getSw360UserFromAuthentication(); Release sw360Release = releaseService.getReleaseForUserById(id, sw360User); HalResource halRelease = createHalReleaseResource(sw360Release, true); - Map releaseIdToRelationship = sw360Release.getReleaseIdToRelationship(); - if (releaseIdToRelationship != null) { - List listOfLinkedRelease = releaseIdToRelationship.keySet().stream() - .map(linkedReleaseId -> wrapTException( - () -> releaseService.getReleaseForUserById(linkedReleaseId, sw360User))) - .collect(Collectors.toList()); - restControllerHelper.addEmbeddedReleases(halRelease, listOfLinkedRelease); + restControllerHelper.addEmbeddedDataToHalResourceRelease(halRelease, sw360Release); + List linkedReleaseRelations = releaseService.getLinkedReleaseRelations(sw360Release, sw360User); + if (linkedReleaseRelations != null) { + restControllerHelper.addEmbeddedReleaseLinks(halRelease, linkedReleaseRelations); } return new ResponseEntity<>(halRelease, HttpStatus.OK); } @@ -607,7 +605,7 @@ private HalResource createHalReleaseResource(Release release, boolean v } if (release.getVendor() != null) { Vendor vendor = release.getVendor(); - HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor.getFullname()); + HalResource vendorHalResource = restControllerHelper.addEmbeddedVendor(vendor); halRelease.addEmbeddedResource("sw360:vendors", vendorHalResource); release.setVendor(null); } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java index 70c04e3e51..478a05a698 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/release/Sw360ReleaseService.java @@ -29,14 +29,8 @@ import org.eclipse.sw360.datahandler.thrift.ThriftClients; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; -import org.eclipse.sw360.datahandler.thrift.components.Component; +import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; -import org.eclipse.sw360.datahandler.thrift.components.ComponentType; -import org.eclipse.sw360.datahandler.thrift.components.ExternalTool; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStatus; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStep; -import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.fossology.FossologyService; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -57,16 +51,13 @@ import org.springframework.util.FileCopyUtils; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyString; import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -118,6 +109,22 @@ public Release getReleaseForUserById(String releaseId, User sw360User) throws TE return releaseById; } + public List getLinkedReleaseRelations(Release release, User user) throws TException { + List linkedReleaseRelations = getLinkedReleaseRelationsWithAccessibility(release, user); + linkedReleaseRelations = linkedReleaseRelations.stream().filter(Objects::nonNull).sorted(Comparator.comparing( + rl -> rl.isAccessible() ? SW360Utils.getVersionedName(nullToEmptyString(rl.getName()), rl.getVersion()) : "~", String.CASE_INSENSITIVE_ORDER) + ).collect(Collectors.toList()); + return linkedReleaseRelations; + } + + public List getLinkedReleaseRelationsWithAccessibility(Release release, User user) throws TException { + if (release != null && release.getReleaseIdToRelationship() != null) { + ComponentService.Iface componentClient = getThriftComponentClient(); + return componentClient.getLinkedReleaseRelationsWithAccessibility(release.getReleaseIdToRelationship(), user); + } + return Collections.emptyList(); + } + public Release setComponentDependentFieldsInRelease(Release releaseById, User sw360User) { String componentId = releaseById.getComponentId(); if (CommonUtils.isNullEmptyOrWhitespace(componentId)) { diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java index b87979bc29..69fb4eda6e 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ReleaseSpecTest.java @@ -15,17 +15,9 @@ import org.eclipse.sw360.datahandler.thrift.*; import org.eclipse.sw360.datahandler.thrift.attachments.*; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; +import org.eclipse.sw360.datahandler.thrift.components.*; +import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; import org.eclipse.sw360.rest.resourceserver.attachment.AttachmentInfo; -import org.eclipse.sw360.datahandler.thrift.components.COTSDetails; -import org.eclipse.sw360.datahandler.thrift.components.ClearingInformation; -import org.eclipse.sw360.datahandler.thrift.components.ClearingState; -import org.eclipse.sw360.datahandler.thrift.components.Component; -import org.eclipse.sw360.datahandler.thrift.components.ComponentType; -import org.eclipse.sw360.datahandler.thrift.components.ExternalTool; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcess; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStatus; -import org.eclipse.sw360.datahandler.thrift.components.ExternalToolProcessStep; -import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectType; @@ -177,6 +169,17 @@ public void before() throws TException, IOException { releaseExternalIds.put("mainline-id-component", "1432"); releaseExternalIds.put("ws-component-id", "[\"2365\",\"5487923\"]"); + EccInformation eccInformation = new EccInformation(); + eccInformation.setAl("AL"); + eccInformation.setEccn("ECCN"); + eccInformation.setAssessorContactPerson("admin@sw360.org"); + eccInformation.setAssessorDepartment("DEPARTMENT"); + eccInformation.setEccComment("Set ECC"); + eccInformation.setMaterialIndexNumber("12"); + eccInformation.setAssessmentDate("2023-06-27"); + eccInformation.setEccStatus(ECCStatus.OPEN); + + release.setId(releaseId); owner.setReleaseId(release.getId()); release.setName("Spring Core 4.3.4"); @@ -186,7 +189,10 @@ public void before() throws TException, IOException { release.setCreatedOn("2016-12-18"); release.setCreatedBy("admin@sw360.org"); release.setModerators(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); + release.setSubscribers(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); + release.setContributors(new HashSet<>(Arrays.asList("admin@sw360.org", "jane@sw360.org"))); release.setCreatedBy("admin@sw360.org"); + release.setModifiedBy("admin@sw360.org"); release.setSourceCodeDownloadurl("http://www.google.com"); release.setBinaryDownloadurl("http://www.google.com/binaries"); release.setComponentId(component.getId()); @@ -201,6 +207,7 @@ public void before() throws TException, IOException { release.setOtherLicenseIds(new HashSet<>(Arrays.asList("MIT", "BSD-3-Clause"))); release.setOperatingSystems(ImmutableSet.of("Windows", "Linux")); release.setSoftwarePlatforms(new HashSet<>(Arrays.asList("Java SE", ".NET"))); + release.setEccInformation(eccInformation); releaseList.add(release); Release release2 = new Release(); @@ -230,6 +237,7 @@ public void before() throws TException, IOException { release2.setReleaseIdToRelationship(releaseIdToRelationship); release2.setClearingInformation(clearingInfo); release2.setCotsDetails(cotsDetails); + release2.setEccInformation(eccInformation); releaseList.add(release2); release3 = new Release(); @@ -479,9 +487,9 @@ public void should_document_get_release_all_details() throws Exception { subsectionWithPath("_embedded.sw360:releases.[]sourceCodeDownloadurl").description("the source code download url of the release"), subsectionWithPath("_embedded.sw360:releases.[]binaryDownloadurl").description("the binary download url of the release"), subsectionWithPath("_embedded.sw360:releases.[]externalIds").description("When releases are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), + subsectionWithPath("_embedded.sw360:releases.[]eccInformation").description("The eccInformation of this release"), subsectionWithPath("_embedded.sw360:releases.[]additionalData").description("A place to store additional data used by external tools").optional(), subsectionWithPath("_embedded.sw360:releases.[]languages").description("The language of the component"), - subsectionWithPath("_embedded.sw360:releases.[]contributors").description("An array of all project contributors with email").optional(), subsectionWithPath("_embedded.sw360:releases.[]mainLicenseIds").description("An array of all main licenses").optional(), subsectionWithPath("_embedded.sw360:releases.[]otherLicenseIds").description("An array of all other licenses associated with the release").optional(), subsectionWithPath("_embedded.sw360:releases.[]operatingSystems").description("The OS on which the release operates"), @@ -577,6 +585,7 @@ public void should_document_get_release() throws Exception { fieldWithPath("createdOn").description("The creation date of the internal sw360 release"), fieldWithPath("componentType").description("The componentType of the release, possible values are " + Arrays.asList(ComponentType.values())), fieldWithPath("mainlineState").description("the mainline state of the release, possible values are: " + Arrays.asList(MainlineState.values())), + subsectionWithPath("eccInformation").description("The eccInformation of this release"), fieldWithPath("sourceCodeDownloadurl").description("the source code download url of the release"), fieldWithPath("binaryDownloadurl").description("the binary download url of the release"), fieldWithPath("otherLicenseIds").description("An array of all other licenses associated with the release"), @@ -587,6 +596,10 @@ public void should_document_get_release() throws Exception { fieldWithPath("operatingSystems").description("The OS on which the release operates"), fieldWithPath("softwarePlatforms").description("The software platforms of the component"), subsectionWithPath("_embedded.sw360:moderators").description("An array of all release moderators with email and link to their <>"), + subsectionWithPath("_embedded.sw360:subscribers").description("An array of all release subscribers with email and link to their <>"), + subsectionWithPath("_embedded.sw360:contributors").description("An array of all release contributors with email and link to their <>"), + subsectionWithPath("_embedded.sw360:modifiedBy").description("A release modifiedBy with email and link to their <>"), + subsectionWithPath("_embedded.sw360:createdBy").description("A release createdBy with email and link to their <>"), subsectionWithPath("_embedded.sw360:attachments").description("An array of all release attachments and link to their <>"), subsectionWithPath("_links").description("<> to other resources") ))); @@ -883,6 +896,7 @@ private RestDocumentationResultHandler documentReleaseProperties() { fieldWithPath("createdOn").description("The creation date of the internal sw360 release"), fieldWithPath("mainlineState").description("the mainline state of the release, possible values are: " + Arrays.asList(MainlineState.values())), fieldWithPath("sourceCodeDownloadurl").description("the source code download url of the release"), + subsectionWithPath("eccInformation").description("The eccInformation of this release"), fieldWithPath("binaryDownloadurl").description("the binary download url of the release"), fieldWithPath("otherLicenseIds").description("An array of all other licenses associated with the release"), subsectionWithPath("externalIds").description("When releases are imported from other tools, the external ids can be stored here. Store as 'Single String' when single value, or 'Array of String' when multi-values"), From 1bf157600e08667c79f83baaf317f55d424b65e3 Mon Sep 17 00:00:00 2001 From: Abdul Kapti Date: Fri, 7 Aug 2020 11:14:09 +0530 Subject: [PATCH 13/17] feat(UI/REST): CycloneDX SBOM Importer & Exporter Signed-off-by: afsahsyeda Signed-off-by: akapti --- backend/src-common/pom.xml | 8 + .../sw360/cyclonedx/CycloneDxBOMExporter.java | 294 +++++++++ .../sw360/cyclonedx/CycloneDxBOMImporter.java | 560 ++++++++++++++++++ .../db/ComponentDatabaseHandler.java | 23 +- .../datahandler/db/ComponentRepository.java | 56 +- .../datahandler/db/DatabaseHandlerUtil.java | 30 +- .../db/ProjectDatabaseHandler.java | 56 +- .../datahandler/db/ReleaseRepository.java | 43 +- .../sw360/projects/ProjectHandler.java | 25 +- frontend/sw360-portlet/pom.xml | 4 + .../sw360/portal/common/PortalConstants.java | 8 +- .../sw360/portal/common/PortletUtils.java | 6 + .../portlets/projects/ProjectPortlet.java | 102 +++- .../resources/css/components/_tooltips.scss | 37 ++ .../resources/html/admin/oauthclient/view.jsp | 2 +- .../includes/components/detailOverview.jspf | 2 +- .../includes/components/editBasicInfo.jspf | 40 +- .../includes/components/summary.jspf | 4 + .../includes/releases/detailOverview.jspf | 2 +- .../resources/html/components/view.jsp | 2 +- .../projects/includes/detailOverview.jspf | 192 +++++- .../includes/projects/clearingStatus.jsp | 4 +- .../html/projects/includes/searchProjects.jsp | 4 +- .../META-INF/resources/html/projects/view.jsp | 12 +- .../META-INF/resources/html/search/view.jsp | 2 +- .../html/utils/includes/attachmentsDetail.jsp | 147 ++++- .../html/utils/includes/importBom.jspf | 281 +++++++-- .../html/utils/includes/searchReleases.jsp | 2 +- .../resources/js/utils/includes/clipboard.js | 23 +- .../resources/content/Language.properties | 104 +++- .../resources/content/Language_ja.properties | 99 +++- .../resources/content/Language_vi.properties | 100 +++- .../resources/content/Language_zh.properties | 102 +++- .../sw360/portal/portlets/base.properties | 2 + .../src/main/resources/sw360.properties | 12 + libraries/datahandler/pom.xml | 4 + .../datahandler/common/SW360Constants.java | 16 + .../sw360/datahandler/common/SW360Utils.java | 48 +- .../datahandler/common/ThriftEnumUtils.java | 15 +- .../src/main/thrift/components.thrift | 2 + .../src/main/thrift/projects.thrift | 15 + .../datahandler/src/main/thrift/sw360.thrift | 11 + pom.xml | 12 +- .../src/docs/asciidoc/projects.adoc | 43 +- .../core/JacksonCustomizations.java | 5 +- .../project/ProjectController.java | 81 ++- .../project/Sw360ProjectService.java | 16 +- .../restdocs/ProjectSpecTest.java | 103 +++- 48 files changed, 2522 insertions(+), 239 deletions(-) create mode 100644 backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMExporter.java create mode 100644 backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java diff --git a/backend/src-common/pom.xml b/backend/src-common/pom.xml index 9f8ce85d44..46b95cb588 100644 --- a/backend/src-common/pom.xml +++ b/backend/src-common/pom.xml @@ -55,5 +55,13 @@ org.apache.commons commons-lang3 + + org.cyclonedx + cyclonedx-core-java + + + com.github.package-url + packageurl-java + diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMExporter.java b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMExporter.java new file mode 100644 index 0000000000..9e5c5f0521 --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMExporter.java @@ -0,0 +1,294 @@ +/* + * Copyright Siemens Healthineers GmBH, 2023. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.sw360.cyclonedx; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.apache.jena.ext.com.google.common.collect.Sets; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.cyclonedx.exception.GeneratorException; +import org.cyclonedx.generators.json.BomJsonGenerator14; +import org.cyclonedx.generators.xml.BomXmlGenerator14; +import org.cyclonedx.model.Bom; +import org.cyclonedx.model.Component.Type; +import org.cyclonedx.model.ExternalReference; +import org.cyclonedx.model.License; +import org.cyclonedx.model.LicenseChoice; +import org.cyclonedx.model.Metadata; +import org.cyclonedx.model.Tool; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.SW360Constants; +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.db.ComponentDatabaseHandler; +import org.eclipse.sw360.datahandler.db.ProjectDatabaseHandler; +import org.eclipse.sw360.datahandler.thrift.CycloneDxComponentType; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.RequestSummary; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.ThriftClients; +import org.eclipse.sw360.datahandler.thrift.components.Component; +import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.projects.Project; +import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; +import org.eclipse.sw360.datahandler.thrift.users.User; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; + +/** + * CycloneDX BOM export implementation. + * Supports both XML and JSON format of CycloneDX SBOM + * + * @author abdul.kapti@siemens-healthineers.com + */ +public class CycloneDxBOMExporter { + + private static final Logger log = LogManager.getLogger(CycloneDxBOMExporter.class); + private final ProjectDatabaseHandler projectDatabaseHandler; + private final ComponentDatabaseHandler componentDatabaseHandler; + private final User user; + + public CycloneDxBOMExporter(ProjectDatabaseHandler projectDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler, User user) { + this.projectDatabaseHandler = projectDatabaseHandler; + this.componentDatabaseHandler = componentDatabaseHandler; + this.user = user; + } + + public RequestSummary exportSbom(String projectId, String bomType, Boolean includeSubProjReleases, User user) { + final RequestSummary summary = new RequestSummary(RequestStatus.SUCCESS); + try { + Project project = projectDatabaseHandler.getProjectById(projectId, user); + Bom bom = new Bom(); + Set linkedReleaseIds = Sets.newHashSet(CommonUtils.getNullToEmptyKeyset(project.getReleaseIdToUsage())); + + if (!SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)) { + log.warn("User does not have permission to export the SBOM: " + user.getEmail()); + summary.setRequestStatus(RequestStatus.ACCESS_DENIED); + return summary; + } + + if (includeSubProjReleases && project.getLinkedProjectsSize() > 0) { + ProjectService.Iface client = new ThriftClients().makeProjectClient(); + linkedReleaseIds.addAll(SW360Utils.getLinkedReleaseIdsOfAllSubProjectsAsFlatList(project, Sets.newHashSet(), Sets.newHashSet(), client, user)); + } + + if (CommonUtils.isNotEmpty(linkedReleaseIds)) { + List linkedReleases = componentDatabaseHandler.getReleasesByIds(linkedReleaseIds); + Set componentIds = linkedReleases.stream().map(Release::getComponentId).filter(Objects::nonNull).collect(Collectors.toSet()); + List components = componentDatabaseHandler.getComponentsByIds(componentIds); + List sbomComponents = getCycloneDxComponentsFromSw360Releases(linkedReleases, components); + bom.setComponents(sbomComponents); + } else { + log.warn("Cannot export SBOM for project without linked releases: " + projectId); + summary.setRequestStatus(RequestStatus.FAILED_SANITY_CHECK); + return summary; + } + + org.cyclonedx.model.Component metadataComp = getMetadataComponent(project); + Tool tool = getTool(); + Metadata metadata = new Metadata(); + metadata.setComponent(metadataComp); + metadata.setTimestamp(new Date()); + metadata.setTools(Lists.newArrayList(tool)); + bom.setMetadata(metadata); + + if (SW360Constants.JSON_FILE_EXTENSION.equalsIgnoreCase(bomType)) { + BomJsonGenerator14 jsonBom = new BomJsonGenerator14(bom); + summary.setMessage(jsonBom.toJsonString()); + } else { + BomXmlGenerator14 xmlBom = new BomXmlGenerator14(bom); + summary.setMessage(xmlBom.toXmlString()); + } + return summary; + } catch (SW360Exception e) { + log.error(String.format("An error occured while fetching project: %s from db, for SBOM export!", projectId), e); + summary.setMessage("An error occured while fetching project from db, for SBOM export: " + e.getMessage()); + } catch (GeneratorException e) { + log.error(String.format("An error occured while exporting xml SBOM for project with id: %s", projectId), e); + summary.setMessage("An error occured while exporting xml SBOM for project: " + e.getMessage()); + } catch (Exception e) { + log.error("An error occured while exporting SBOm for project: " + projectId, e); + summary.setMessage("An error occured while exporting SBOM for project: " + e.getMessage()); + } + summary.setRequestStatus(RequestStatus.FAILURE); + return summary; + } + + private static Tool getTool() { + Tool tool = new Tool(); + tool.setName(SW360Constants.TOOL_NAME); + tool.setVendor(SW360Constants.TOOL_VENDOR); + tool.setVersion(SW360Utils.getSW360Version()); + return tool; + } + + private org.cyclonedx.model.Component getMetadataComponent(Project project) { + org.cyclonedx.model.Component component = new org.cyclonedx.model.Component(); + component.setAuthor(user.getEmail()); + component.setDescription(CommonUtils.nullToEmptyString(project.getDescription())); + component.setName(project.getName()); + component.setVersion(CommonUtils.nullToEmptyString(project.getVersion())); + component.setType(Type.APPLICATION); + component.setGroup(CommonUtils.nullToEmptyString(project.getBusinessUnit())); + return component; + } + + private List getCycloneDxComponentsFromSw360Releases(List releases, List components) { + List comps = Lists.newArrayList(); + Map compIdToComponentMap = components.stream() + .map(comp -> new AbstractMap.SimpleEntry<>(comp.getId(), comp)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal)); + for (Release release : releases) { + Component sw360Comp = compIdToComponentMap.get(release.getComponentId()); + org.cyclonedx.model.Component comp = new org.cyclonedx.model.Component(); + comp.setName(release.getName()); + comp.setVersion(release.getVersion()); + comp.setDescription(CommonUtils.nullToEmptyString(sw360Comp.getDescription())); + + // set package URL + Set purlSet = new TreeSet<>(); + if (!CommonUtils.isNullOrEmptyMap(release.getExternalIds())) { + if (release.getExternalIds().containsKey(SW360Constants.PACKAGE_URL)) { + purlSet.addAll(getPurlFromSw360Document(release.getExternalIds(), SW360Constants.PACKAGE_URL)); + } + if ( release.getExternalIds().containsKey(SW360Constants.PURL_ID)) { + purlSet.addAll(getPurlFromSw360Document(release.getExternalIds(), SW360Constants.PURL_ID)); + } + } else if (!CommonUtils.isNullOrEmptyMap(sw360Comp.getExternalIds())) { + if (sw360Comp.getExternalIds().containsKey(SW360Constants.PACKAGE_URL)) { + purlSet.addAll(getPurlFromSw360Document(sw360Comp.getExternalIds(), SW360Constants.PACKAGE_URL)); + } + if ( sw360Comp.getExternalIds().containsKey(SW360Constants.PURL_ID)) { + purlSet.addAll(getPurlFromSw360Document(sw360Comp.getExternalIds(), SW360Constants.PURL_ID)); + } + } + if (CommonUtils.isNotEmpty(purlSet)) { + comp.setPurl(String.join(", ", purlSet)); + } + + // set CycloneDx component type + if (sw360Comp.isSetCdxComponentType()) { + comp.setType(getCdxComponentType(sw360Comp.getCdxComponentType())); + } + + // set vcs, website and mailing list + List extRefs = Lists.newArrayList(); + if (null != release.getRepository() && null != release.getRepository().getUrl()) { + ExternalReference extRef = new ExternalReference(); + extRef.setType(org.cyclonedx.model.ExternalReference.Type.VCS); + extRef.setUrl(release.getRepository().getUrl()); + extRefs.add(extRef); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getHomepage())) { + ExternalReference extRef = new ExternalReference(); + extRef.setType(org.cyclonedx.model.ExternalReference.Type.WEBSITE); + extRef.setUrl(sw360Comp.getHomepage()); + extRefs.add(extRef); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getMailinglist())) { + ExternalReference extRef = new ExternalReference(); + extRef.setType(org.cyclonedx.model.ExternalReference.Type.MAILING_LIST); + extRef.setUrl(sw360Comp.getMailinglist()); + extRefs.add(extRef); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getWiki())) { + ExternalReference extRef = new ExternalReference(); + extRef.setType(org.cyclonedx.model.ExternalReference.Type.SUPPORT); + extRef.setUrl(sw360Comp.getWiki()); + extRefs.add(extRef); + } + if (CommonUtils.isNotEmpty(extRefs)) { + comp.setExternalReferences(extRefs); + } + + // set licenses + Set licenses = Sets.newHashSet(); + if (CommonUtils.isNotEmpty(release.getMainLicenseIds())) { + licenses.addAll(release.getMainLicenseIds()); + } + if (CommonUtils.isNotEmpty(release.getOtherLicenseIds())) { + licenses.addAll(release.getOtherLicenseIds()); + } + if (CommonUtils.isNotEmpty(sw360Comp.getMainLicenseIds())) { + licenses.addAll(sw360Comp.getMainLicenseIds()); + } + if (CommonUtils.isNotEmpty(licenses)) { + comp.setLicenseChoice(getLicenseFromSw360Document(licenses)); + } + + comps.add(comp); + } + return comps; + } + + + private LicenseChoice getLicenseFromSw360Document(Set sw360Licenses) { + LicenseChoice licenseChoice = new LicenseChoice(); + List licenses = Lists.newArrayList(); + for (String lic : sw360Licenses) { + License license = new License(); + license.setId(lic); + licenses.add(license); + } + licenseChoice.setLicenses(licenses); + return licenseChoice; + } + + private org.cyclonedx.model.Component.Type getCdxComponentType(CycloneDxComponentType compType) { + switch (compType) { + case APPLICATION: + return org.cyclonedx.model.Component.Type.APPLICATION; + case CONTAINER: + return org.cyclonedx.model.Component.Type.CONTAINER; + case DEVICE: + return org.cyclonedx.model.Component.Type.DEVICE; + case FILE: + return org.cyclonedx.model.Component.Type.FILE; + case FIRMWARE: + return org.cyclonedx.model.Component.Type.FIRMWARE; + case FRAMEWORK: + return org.cyclonedx.model.Component.Type.FRAMEWORK; + case LIBRARY: + return org.cyclonedx.model.Component.Type.LIBRARY; + case OPERATING_SYSTEM: + return org.cyclonedx.model.Component.Type.OPERATING_SYSTEM; + default: + return null; + } + } + + @SuppressWarnings("unchecked") + private Set getPurlFromSw360Document(Map externalIds, String key) { + String existingPurl = CommonUtils.nullToEmptyMap(externalIds).getOrDefault(key, ""); + Set purlSet = Sets.newHashSet(); + if (CommonUtils.isNotNullEmptyOrWhitespace(existingPurl)) { + ObjectMapper mapper = new ObjectMapper(); + try { + if (existingPurl.equals(SW360Constants.NULL_STRING)) { + purlSet.add(SW360Constants.NULL_STRING); + } else { + purlSet = mapper.readValue(existingPurl, Set.class); + } + } catch (IOException e) { + purlSet.add(existingPurl); + } + } + return purlSet; + } +} diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java new file mode 100644 index 0000000000..2f2288da82 --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java @@ -0,0 +1,560 @@ +/* + * Copyright Siemens Healthineers GmBH, 2023. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.sw360.cyclonedx; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.thrift.TException; +import org.cyclonedx.exception.ParseException; +import org.cyclonedx.model.Bom; +import org.cyclonedx.model.ExternalReference; +import org.cyclonedx.model.ExternalReference.Type; +import org.cyclonedx.model.Metadata; +import org.cyclonedx.parsers.JsonParser; +import org.cyclonedx.parsers.Parser; +import org.cyclonedx.parsers.XmlParser; +import org.eclipse.sw360.commonIO.AttachmentFrontendUtils; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.SW360Constants; +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.common.ThriftEnumUtils; +import org.eclipse.sw360.datahandler.couchdb.AttachmentConnector; +import org.eclipse.sw360.datahandler.db.ComponentDatabaseHandler; +import org.eclipse.sw360.datahandler.db.ProjectDatabaseHandler; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; +import org.eclipse.sw360.datahandler.thrift.CycloneDxComponentType; +import org.eclipse.sw360.datahandler.thrift.MainlineState; +import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; +import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.RequestSummary; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.Visibility; +import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentContent; +import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; +import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; +import org.eclipse.sw360.datahandler.thrift.components.Component; +import org.eclipse.sw360.datahandler.thrift.components.ComponentType; +import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.components.Repository; +import org.eclipse.sw360.datahandler.thrift.components.RepositoryType; +import org.eclipse.sw360.datahandler.thrift.projects.Project; +import org.eclipse.sw360.datahandler.thrift.projects.ProjectType; +import org.eclipse.sw360.datahandler.thrift.users.User; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.github.jsonldjava.shaded.com.google.common.io.Files; +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import com.google.common.net.MediaType; +import com.google.gson.Gson; + +/** + * CycloneDX BOM import implementation. + * Supports both XML and JSON format of CycloneDX SBOM + * + * @author abdul.kapti@siemens-healthineers.com + */ +public class CycloneDxBOMImporter { + private static final Logger log = LogManager.getLogger(CycloneDxBOMImporter.class); + private static final String DOT = "."; + private static final String HYPHEN = "-"; + private static final String JOINER = "||"; + private static final String COLON_REGEX = "[:,\\s]"; + private static final String COMP_CREATION_COUNT_KEY = "compCreationCount"; + private static final String COMP_REUSE_COUNT_KEY = "compReuseCount"; + private static final String REL_CREATION_COUNT_KEY = "relCreationCount"; + private static final String REL_REUSE_COUNT_KEY = "relReuseCount"; + private static final String DUPLICATE_COMPONENT = "dupComp"; + private static final String DUPLICATE_RELEASE = "dupRel"; + private static final String INVALID_RELEASE = "invalidRel"; + private static final String PROJECT_ID = "projectId"; + private static final String PROJECT_NAME = "projectName"; + + private final ProjectDatabaseHandler projectDatabaseHandler; + private final ComponentDatabaseHandler componentDatabaseHandler; + private final User user; + private final AttachmentConnector attachmentConnector; + + public CycloneDxBOMImporter(ProjectDatabaseHandler projectDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler, + AttachmentConnector attachmentConnector, User user) { + this.projectDatabaseHandler = projectDatabaseHandler; + this.componentDatabaseHandler = componentDatabaseHandler; + this.attachmentConnector = attachmentConnector; + this.user = user; + } + + @SuppressWarnings("unchecked") + public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user) { + RequestSummary requestSummary = new RequestSummary(); + Map messageMap = new HashMap<>(); + requestSummary.setRequestStatus(RequestStatus.FAILURE); + String fileExtension = Files.getFileExtension(attachmentContent.getFilename()); + Parser parser; + + if (!SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)) { + log.warn("User does not have permission to import the SBOM: " + user.getEmail()); + requestSummary.setMessage(SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE.name()); + requestSummary.setRequestStatus(RequestStatus.ACCESS_DENIED); + return requestSummary; + } + + if (fileExtension.equalsIgnoreCase(SW360Constants.XML_FILE_EXTENSION)) { + parser = new XmlParser(); + } else if (fileExtension.equalsIgnoreCase(SW360Constants.JSON_FILE_EXTENSION)) { + parser = new JsonParser(); + } else { + requestSummary.setMessage(String.format("Invalid file format %s. Only XML & JSON SBOM are supported by CycloneDX!", fileExtension)); + return requestSummary; + } + + try { + // parsing the input stream into CycloneDx org.cyclonedx.model.Bom + Bom bom = parser.parse(IOUtils.toByteArray(inputStream)); + Metadata bomMetadata = bom.getMetadata(); + + // Getting List of org.cyclonedx.model.Component from the Bom + List components = CommonUtils.nullToEmptyList(bom.getComponents()); + + org.cyclonedx.model.Component compMetadata = bomMetadata.getComponent(); + Map> vcsToComponentMap = new HashMap<>(); + + vcsToComponentMap.put("", components); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + + if (RequestStatus.SUCCESS.equals(requestSummary.getRequestStatus())) { + String jsonMessage = requestSummary.getMessage(); + messageMap = new Gson().fromJson(jsonMessage, Map.class); + String projId = messageMap.get("projectId"); + Project project = projectDatabaseHandler.getProjectById(projId, user); + try { + // link SBOM attachment to Project + if (attachmentContent != null) { + Attachment attachment = makeAttachmentFromContent(attachmentContent); + project.addToAttachments(attachment); + } + RequestStatus updateStatus = projectDatabaseHandler.updateProject(project, user, true); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("SBOM attachment linked to project successfully: " + project.getId()); + } else { + log.info("failed to link SBOM Import status attachment to project with status: " + updateStatus); + } + messageMap.put("result", requestSummary.getRequestStatus().toString()); + messageMap.put("fileName", attachmentContent.getFilename()); + final StringBuilder fileName = new StringBuilder(attachmentContent.getFilename()) + .append("_ImportStatus_").append(SW360Utils.getCreatedOnTime().replaceAll(COLON_REGEX, HYPHEN)).append(DOT).append(SW360Constants.JSON_FILE_EXTENSION); + final InputStream inStream = IOUtils.toInputStream(convertCollectionToJSONString(messageMap), Charset.defaultCharset()); + final AttachmentContent importResultAttachmentContent = makeAttachmentContent(fileName.toString(), MediaType.JSON_UTF_8.toString()); + Attachment attachment = new AttachmentFrontendUtils().uploadAttachmentContent(importResultAttachmentContent, inStream, user); + attachment.setAttachmentType(AttachmentType.OTHER); + StringBuilder comment = new StringBuilder("Auto Generated: CycloneDX SBOM import result for: '").append(attachmentContent.getFilename()).append("'"); + attachment.setCreatedComment(comment.toString()); + attachment.setSha1(attachmentConnector.getSha1FromAttachmentContentId(importResultAttachmentContent.getId())); + project = projectDatabaseHandler.getProjectById(projId, user); + project.addToAttachments(attachment); + updateStatus = projectDatabaseHandler.updateProject(project, user, true); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("SBOM Import status attachment linked to project successfully: " + project.getId()); + } else { + log.info("failed to link SBOM Import status attachment to project with status: " + updateStatus); + } + } catch (SW360Exception e) { + log.error("An error occured while updating project from SBOM: " + e.getMessage()); + requestSummary.setMessage("An error occured while updating project during SBOM import, please delete the project and re-import SBOM!"); + return requestSummary; + } + } + + } catch (IOException e) { + log.error("IOException occured while importing CycloneDX SBoM: ", e); + requestSummary.setMessage("IOException occured while importing CycloneDX SBoM: " + e.getMessage()); + } catch (ParseException e) { + log.error("ParseException occured while importing CycloneDX SBoM: ", e); + requestSummary.setMessage("ParseException occured while importing CycloneDX SBoM: " + e.getMessage()); + } catch (SW360Exception e) { + log.error("SW360Exception occured while importing CycloneDX SBoM: ", e); + requestSummary.setMessage("SW360Exception occured while importing CycloneDX SBoM: " + e.getMessage()); + } catch (Exception e) { + log.error("Exception occured while importing CycloneDX SBoM: ", e); + requestSummary.setMessage("An Exception occured while importing CycloneDX SBoM: " + e.getMessage()); + } + return requestSummary; + } + + public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMetadata, + Map> vcsToComponentMap, String projectId, AttachmentContent attachmentContent) + throws SW360Exception { + final RequestSummary summary = new RequestSummary(); + summary.setRequestStatus(RequestStatus.FAILURE); + + Project project; + AddDocumentRequestSummary projectAddSummary = new AddDocumentRequestSummary(); + AddDocumentRequestStatus addStatus = projectAddSummary.getRequestStatus(); + Map messageMap = new HashMap<>(); + + try { + if (CommonUtils.isNotNullEmptyOrWhitespace(projectId)) { + project = projectDatabaseHandler.getProjectById(projectId, user); + Project sBomProject = createProject(compMetadata); + if (!(sBomProject.getName().equalsIgnoreCase(project.getName()) && sBomProject.getVersion().equalsIgnoreCase(project.getVersion()))) { + log.warn("cannot import SBOM with different metadata information than the current project!"); + summary.setRequestStatus(RequestStatus.FAILED_SANITY_CHECK); + messageMap.put(PROJECT_NAME, SW360Utils.getVersionedName(sBomProject.getName(), sBomProject.getVersion())); + summary.setMessage(convertCollectionToJSONString(messageMap)); + return summary; + } + log.info("reusing existing project: " + projectId); + } else { + // Metadata component is used to created the project + project = createProject(compMetadata); + projectAddSummary = projectDatabaseHandler.addProject(project, user); + addStatus = projectAddSummary.getRequestStatus(); + + if (CommonUtils.isNotNullEmptyOrWhitespace(projectAddSummary.getId())) { + if (AddDocumentRequestStatus.SUCCESS.equals(addStatus)) { + project = projectDatabaseHandler.getProjectById(projectAddSummary.getId(), user); + log.info("project created successfully: " + projectAddSummary.getId()); + } else if (AddDocumentRequestStatus.DUPLICATE.equals(addStatus)) { + log.warn("cannot import SBOM for an existing project from Project List / Home page - " + projectAddSummary.getId()); + summary.setRequestStatus(getRequestStatusFromAddDocRequestStatus(addStatus)); + messageMap.put(PROJECT_ID, projectAddSummary.getId()); + messageMap.put(PROJECT_NAME, SW360Utils.getVersionedName(project.getName(), project.getVersion())); + summary.setMessage(convertCollectionToJSONString(messageMap)); + return summary; + } + } else { + summary.setRequestStatus(getRequestStatusFromAddDocRequestStatus(addStatus)); + summary.setMessage("Invalid Projct metadata present in SBOM or Multiple project with same name and version is already present in SW360!"); + return summary; + } + + } + } catch (SW360Exception e) { + log.error("An error occured while importing project from SBOM: " + e.getMessage()); + summary.setMessage("An error occured while importing project from SBOM!"); + return summary; + } + + messageMap = importAllComponentsAsReleases(vcsToComponentMap, project); + RequestStatus updateStatus = projectDatabaseHandler.updateProject(project, user, true); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("project updated successfully: " + project.getId()); + } else { + log.info("failed to update project with status: " + updateStatus); + } + summary.setMessage(convertCollectionToJSONString(messageMap)); + summary.setRequestStatus(RequestStatus.SUCCESS); + return summary; + } + + private Map importAllComponentsAsReleases(Map> vcsToComponentMap, Project project) { + + final var countMap = new HashMap(); + final Set duplicateComponents = new HashSet<>(); + final Set duplicateReleases = new HashSet<>(); + final Set invalidReleases = new HashSet<>(); + final Map releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage(); + countMap.put(COMP_CREATION_COUNT_KEY, 0); countMap.put(COMP_REUSE_COUNT_KEY, 0); + countMap.put(REL_CREATION_COUNT_KEY, 0); countMap.put(REL_REUSE_COUNT_KEY, 0); + int compCreationCount = 0, compReuseCount = 0, relCreationCount = 0, relReuseCount = 0; + + final List components = vcsToComponentMap.get(""); + for (org.cyclonedx.model.Component bomComp : components) { + Component comp = createComponent(bomComp); + if (CommonUtils.isNullEmptyOrWhitespace(comp.getName()) ) { + log.error("component name is not present in SBoM: " + project.getId()); + continue; + } + String relName = ""; + AddDocumentRequestSummary compAddSummary; + try { + compAddSummary = componentDatabaseHandler.addComponent(comp, user.getEmail()); + + if (CommonUtils.isNotNullEmptyOrWhitespace(compAddSummary.getId())) { + comp.setId(compAddSummary.getId()); + if (AddDocumentRequestStatus.SUCCESS.equals(compAddSummary.getRequestStatus())) { + compCreationCount++; + } else { + compReuseCount++; + } + } else { + // in case of more than 1 duplicate found, then continue and show error message in UI. + log.warn("found multiple components: " + comp.getName()); + duplicateComponents.add(comp.getName()); + continue; + } + + Release release = new Release(); + Set licenses = getLicenseFromBomComponent(bomComp); + release = createRelease(bomComp, comp, licenses); + if (CommonUtils.isNullEmptyOrWhitespace(release.getVersion()) ) { + log.error("release version is not present in SBoM for component: " + comp.getName()); + invalidReleases.add(comp.getName()); + continue; + } + relName = SW360Utils.getVersionedName(release.getName(), release.getVersion()); + + try { + AddDocumentRequestSummary relAddSummary = componentDatabaseHandler.addRelease(release, user); + if (CommonUtils.isNotNullEmptyOrWhitespace(relAddSummary.getId())) { + release.setId(relAddSummary.getId()); + if (AddDocumentRequestStatus.SUCCESS.equals(relAddSummary.getRequestStatus())) { + relCreationCount++; + } else { + relReuseCount++; + } + } else { + // in case of more than 1 duplicate found, then continue and show error message in UI. + log.warn("found multiple releases: " + relName); + duplicateReleases.add(relName); + continue; + } + releaseRelationMap.putIfAbsent(release.getId(), getDefaultRelation()); + } catch (SW360Exception e) { + log.error("An error occured while creating/adding release from SBOM: " + e.getMessage()); + continue; + } + + // update components specific fields + comp = componentDatabaseHandler.getComponent(compAddSummary.getId(), user); + if (null != bomComp.getType() && null == comp.getCdxComponentType()) { + comp.setCdxComponentType(getCdxComponentType(bomComp.getType())); + } + StringBuilder description = new StringBuilder(); + if (CommonUtils.isNullEmptyOrWhitespace(comp.getDescription()) && CommonUtils.isNotNullEmptyOrWhitespace(bomComp.getDescription())) { + description.append(bomComp.getDescription().trim()); + } else if (CommonUtils.isNotNullEmptyOrWhitespace(bomComp.getDescription())) { + description.append(" || ").append(bomComp.getDescription().trim()); + } + if (CommonUtils.isNotEmpty(comp.getMainLicenseIds())) { + comp.getMainLicenseIds().addAll(licenses); + } else { + comp.setMainLicenseIds(licenses); + } + for (ExternalReference extRef : CommonUtils.nullToEmptyList(bomComp.getExternalReferences())) { + if (Type.WEBSITE.equals(extRef.getType()) && CommonUtils.isNullEmptyOrWhitespace(comp.getHomepage())) { + comp.setHomepage(CommonUtils.nullToEmptyString(extRef.getUrl())); + } else if (Type.MAILING_LIST.equals(extRef.getType()) && CommonUtils.isNullEmptyOrWhitespace(comp.getMailinglist())) { + comp.setMailinglist(CommonUtils.nullToEmptyString(extRef.getUrl())); + } else if (Type.SUPPORT.equals(extRef.getType()) && CommonUtils.isNullEmptyOrWhitespace(comp.getWiki())) { + comp.setWiki(CommonUtils.nullToEmptyString(extRef.getUrl())); + } + } + comp.setDescription(description.toString()); + RequestStatus updateStatus = componentDatabaseHandler.updateComponent(comp, user, true); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("updating component successfull: " + comp.getName()); + } + } catch (SW360Exception e) { + log.error("An error occured while creating/adding component from SBOM: " + e.getMessage()); + continue; + } + } + + project.setReleaseIdToUsage(releaseRelationMap); + final Map messageMap = new HashMap<>(); + messageMap.put(DUPLICATE_COMPONENT, String.join(JOINER, duplicateComponents)); + messageMap.put(DUPLICATE_RELEASE, String.join(JOINER, duplicateReleases)); + messageMap.put(INVALID_RELEASE, String.join(JOINER, invalidReleases)); + messageMap.put(PROJECT_ID, project.getId()); + messageMap.put(PROJECT_NAME, SW360Utils.getVersionedName(project.getName(), project.getVersion())); + messageMap.put(COMP_CREATION_COUNT_KEY, String.valueOf(compCreationCount)); + messageMap.put(COMP_REUSE_COUNT_KEY, String.valueOf(compReuseCount)); + messageMap.put(REL_CREATION_COUNT_KEY, String.valueOf(relCreationCount)); + messageMap.put(REL_REUSE_COUNT_KEY, String.valueOf(relReuseCount)); + return messageMap; + } + + + private Set getLicenseFromBomComponent(org.cyclonedx.model.Component comp) { + Set licenses = new HashSet<>(); + if ((null != comp.getLicenseChoice() && CommonUtils.isNotEmpty(comp.getLicenseChoice().getLicenses()))) { + licenses.addAll(comp.getLicenseChoice().getLicenses().stream() + .map(lic -> (null == lic.getId()) ? lic.getName() : lic.getId()) + .filter(lic -> (null != lic && !SW360Constants.NO_ASSERTION.equalsIgnoreCase(lic))) + .collect(Collectors.toSet())); + } + if (null != comp.getEvidence() && null != comp.getEvidence().getLicenseChoice() + && CommonUtils.isNotEmpty(comp.getEvidence().getLicenseChoice().getLicenses())) { + licenses.addAll(comp.getEvidence().getLicenseChoice().getLicenses().stream() + .map(lic -> (null == lic.getId()) ? lic.getName() : lic.getId()) + .filter(lic -> (null != lic && !SW360Constants.NO_ASSERTION.equalsIgnoreCase(lic))) + .collect(Collectors.toSet())); + } + return licenses; + } + + private String convertCollectionToJSONString(Map map) throws SW360Exception { + try { + JsonFactory factory = new JsonFactory(); + StringWriter jsonObjectWriter = new StringWriter(); + JsonGenerator jsonGenerator = factory.createGenerator(jsonObjectWriter); + jsonGenerator.useDefaultPrettyPrinter(); + jsonGenerator.writeStartObject(); + for (Map.Entry entry : map.entrySet()) { + jsonGenerator.writeStringField(entry.getKey(), entry.getValue()); + } + jsonGenerator.writeEndObject(); + jsonGenerator.close(); + return jsonObjectWriter.toString(); + } catch (IOException e) { + throw new SW360Exception("An exception occured while generating JSON info for BOM import! " + e.getMessage()); + } + } + + private RequestStatus getRequestStatusFromAddDocRequestStatus(AddDocumentRequestStatus status) { + switch (status) { + case SUCCESS: + return RequestStatus.SUCCESS; + case DUPLICATE: + return RequestStatus.DUPLICATE; + case NAMINGERROR: + return RequestStatus.NAMINGERROR; + case INVALID_INPUT: + return RequestStatus.INVALID_INPUT; + default: + return RequestStatus.FAILURE; + } + } + + private AttachmentContent makeAttachmentContent(String filename, String contentType) { + AttachmentContent attachment = new AttachmentContent() + .setContentType(contentType) + .setFilename(filename) + .setOnlyRemote(false); + return makeAttachmentContent(attachment); + } + + private AttachmentContent makeAttachmentContent(AttachmentContent content) { + try { + return new AttachmentFrontendUtils().makeAttachmentContent(content); + } catch (TException e) { + throw new RuntimeException(e); + } + } + + private Attachment makeAttachmentFromContent(AttachmentContent attachmentContent) { + Attachment attachment = new Attachment(); + attachment.setAttachmentContentId(attachmentContent.getId()); + attachment.setAttachmentType(AttachmentType.SBOM); + StringBuilder comment = new StringBuilder("Auto Generated: Used for importing CycloneDX SBOM."); + attachment.setCreatedComment(comment.toString()); + attachment.setFilename(attachmentContent.getFilename()); + attachment.setCreatedOn(SW360Utils.getCreatedOn()); + attachment.setCreatedBy(user.getEmail()); + attachment.setCreatedTeam(user.getDepartment()); + attachment.setCheckStatus(CheckStatus.NOTCHECKED); + return attachment; + } + + private Project createProject(org.cyclonedx.model.Component compMetadata) { + return new Project(CommonUtils.nullToEmptyString(compMetadata.getName()).trim()) + .setVersion(CommonUtils.nullToEmptyString(compMetadata.getVersion()).trim()) + .setDescription(CommonUtils.nullToEmptyString(compMetadata.getDescription()).trim()).setType(ThriftEnumUtils.enumToString(ProjectType.PRODUCT)) + .setBusinessUnit(user.getDepartment()).setVisbility(Visibility.EVERYONE); + } + + private Component createComponent(org.cyclonedx.model.Component componentFromBom) { + Component component = new Component(); + component.setName(CommonUtils.nullToEmptyString(componentFromBom.getName()).trim()); + component.setComponentType(ComponentType.OSS); + if (null != componentFromBom.getType()) { + component.setCdxComponentType(getCdxComponentType(componentFromBom.getType())); + } + + for (ExternalReference extRef : CommonUtils.nullToEmptyList(componentFromBom.getExternalReferences())) { + if (Type.WEBSITE.equals(extRef.getType())) { + component.setHomepage(CommonUtils.nullToEmptyString(extRef.getUrl())); + } else if (Type.MAILING_LIST.equals(extRef.getType())) { + component.setMailinglist(CommonUtils.nullToEmptyString(extRef.getUrl())); + } else if (Type.SUPPORT.equals(extRef.getType())) { + component.setWiki(CommonUtils.nullToEmptyString(extRef.getUrl())); + } + } + return component; + } + + private CycloneDxComponentType getCdxComponentType(org.cyclonedx.model.Component.Type compType) { + switch (compType) { + case APPLICATION: + return CycloneDxComponentType.APPLICATION; + case CONTAINER: + return CycloneDxComponentType.CONTAINER; + case DEVICE: + return CycloneDxComponentType.DEVICE; + case FILE: + return CycloneDxComponentType.FILE; + case FIRMWARE: + return CycloneDxComponentType.FIRMWARE; + case FRAMEWORK: + return CycloneDxComponentType.FRAMEWORK; + case LIBRARY: + return CycloneDxComponentType.LIBRARY; + case OPERATING_SYSTEM: + return CycloneDxComponentType.OPERATING_SYSTEM; + default: + return null; + } + } + + private Release createRelease(org.cyclonedx.model.Component componentFromBom, Component component, Set licenses) { + Release release = new Release(component.getName(), CommonUtils.nullToEmptyString(componentFromBom.getVersion()).trim(), component.getId()); + release.setCreatorDepartment(user.getDepartment()); + if (release.isSetMainLicenseIds()) { + release.getMainLicenseIds().addAll(licenses); + } else { + release.setMainLicenseIds(licenses); + } + if (CommonUtils.isNotNullEmptyOrWhitespace(componentFromBom.getPurl())) { + String purl = componentFromBom.getPurl(); + try { + purl = purl.toLowerCase().trim(); + new PackageURL(purl); + Map externalIds = new HashMap<>(); + externalIds.put(SW360Constants.PACKAGE_URL, purl); + release.setExternalIds(externalIds); + } catch (MalformedPackageURLException e) { + log.error("Malformed PURL for component: " + componentFromBom.getName(), e); + } + } + for (ExternalReference extRef : CommonUtils.nullToEmptyList(componentFromBom.getExternalReferences())) { + if (Type.VCS.equals(extRef.getType())) { + String repoUrl = CommonUtils.nullToEmptyString(extRef.getUrl()); + Repository repo = new Repository(repoUrl); + if (repoUrl.toLowerCase().contains("github")) { + repo.setRepositorytype(RepositoryType.GIT); + } else if (repoUrl.toLowerCase().contains("svn")) { + repo.setRepositorytype(RepositoryType.SVN); + } + release.setRepository(repo); + } + } + return release; + } + + private static ProjectReleaseRelationship getDefaultRelation() { + return new ProjectReleaseRelationship(ReleaseRelationship.UNKNOWN, MainlineState.OPEN); + } +} diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java index b5948ebc9a..29c6f216cc 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentDatabaseHandler.java @@ -67,7 +67,6 @@ import java.io.IOException; import java.io.InputStream; import org.spdx.library.InvalidSPDXAnalysisException; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.util.*; import java.util.concurrent.TimeUnit; @@ -150,6 +149,7 @@ public class ComponentDatabaseHandler extends AttachmentAwareDatabaseHandler { ClearingInformation._Fields.REQUEST_ID, ClearingInformation._Fields.ADDITIONAL_REQUEST_INFO, ClearingInformation._Fields.EXTERNAL_SUPPLIER_ID, ClearingInformation._Fields.EVALUATED, ClearingInformation._Fields.PROC_START); + public ComponentDatabaseHandler(Supplier httpClient, String dbName, String attachmentDbName, ComponentModerator moderator, ReleaseModerator releaseModerator, ProjectModerator projectModerator) throws MalformedURLException { super(httpClient, dbName, attachmentDbName); DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(httpClient, dbName); @@ -185,6 +185,7 @@ public ComponentDatabaseHandler(Supplier httpClient, String dbNa public ComponentDatabaseHandler(Supplier supplier, String dbName, String attachmentDbName) throws MalformedURLException { this(supplier, dbName, attachmentDbName, new ComponentModerator(), new ReleaseModerator(), new ProjectModerator()); } + public ComponentDatabaseHandler(Supplier supplier, String dbName, String changelogsDbName, String attachmentDbName) throws MalformedURLException { this(supplier, dbName, attachmentDbName, new ComponentModerator(), new ReleaseModerator(), new ProjectModerator()); DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(supplier, changelogsDbName); @@ -488,7 +489,7 @@ public AddDocumentRequestSummary addRelease(Release release, User user) throws S if(isDuplicate(release)) { final AddDocumentRequestSummary addDocumentRequestSummary = new AddDocumentRequestSummary() .setRequestStatus(AddDocumentRequestStatus.DUPLICATE); - List duplicates = releaseRepository.searchByNameAndVersion(release.getName(), release.getVersion()); + List duplicates = releaseRepository.searchByNameAndVersion(release.getName(), release.getVersion(), true); if (duplicates.size() == 1) { duplicates.stream() .map(Release::getId) @@ -573,7 +574,7 @@ private boolean isDuplicate(String releaseName, String releaseVersion) { if (isNullEmptyOrWhitespace(releaseName)) { return false; } - List duplicates = releaseRepository.searchByNameAndVersion(releaseName, releaseVersion); + List duplicates = releaseRepository.searchByNameAndVersion(releaseName, releaseVersion, true); return duplicates.size()>0; } @@ -1061,7 +1062,7 @@ public RequestStatus updateRelease(Release release, User user, Iterable permissions = makePermission(actual, user); @@ -1320,7 +1321,7 @@ public RequestSummary updateReleasesDirectly(Set releases, User user) t return RepositoryUtils.doBulk(prepareReleases(releases), user, releaseRepository); } - public RequestStatus updateReleaseFromAdditionsAndDeletions(Release releaseAdditions, Release releaseDeletions, User user){ + public RequestStatus updateReleaseFromAdditionsAndDeletions(Release releaseAdditions, Release releaseDeletions, User user) { try { Release release = getRelease(releaseAdditions.getId(), user); @@ -1968,6 +1969,16 @@ public List getReleases(Set ids) { return releaseRepository.makeSummary(SummaryType.SHORT, ids); } + // return release directly from db, without making summary. + public List getReleasesByIds(Set ids) { + return CommonUtils.isNullOrEmptyCollection(ids) ? Lists.newArrayList() : releaseRepository.get(ids); + } + + // return components directly from db, without making summary. + public List getComponentsByIds(Set ids) { + return CommonUtils.isNullOrEmptyCollection(ids) ? Lists.newArrayList() : componentRepository.get(ids); + } + public List getAccessibleReleases(Set ids, User user) { return getAccessibleReleaseList(releaseRepository.makeSummary(SummaryType.SHORT, ids), user); } @@ -2196,7 +2207,7 @@ public String getCyclicLinkedReleasePath(Release release, User user) throws TExc } public List searchComponentByNameForExport(String name, boolean caseSensitive) { - return componentRepository.searchByNameForExport(name, caseSensitive); + return componentRepository.searchComponentByName(name, caseSensitive); } public Set getUsingComponents(String releaseId) { diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentRepository.java index 3381f8dd92..4f3cd3fbb0 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ComponentRepository.java @@ -37,15 +37,15 @@ */ public class ComponentRepository extends SummaryAwareRepository { private static final String ALL = "function(doc) { if (doc.type == 'component') emit(null, doc._id) }"; - private static final String BYCREATEDON = "function(doc) { if(doc.type == 'component') { emit(doc.createdOn, doc._id) } }"; - private static final String USEDATTACHMENTCONTENTS = "function(doc) { " + + private static final String BY_CREATED_ON = "function(doc) { if(doc.type == 'component') { emit(doc.createdOn, doc._id) } }"; + private static final String USED_ATTACHMENT_CONTENTS = "function(doc) { " + " if(doc.type == 'release' || doc.type == 'component' || doc.type == 'project') {" + " for(var i in doc.attachments){" + " emit(null, doc.attachments[i].attachmentContentId);" + " }" + " }" + "}"; - private static final String MYCOMPONENTS = "function(doc) {" + + private static final String MY_COMPONENTS = "function(doc) {" + " if (doc.type == 'component') {" + " emit(doc.createdBy, doc._id);" + " } " + @@ -57,29 +57,29 @@ public class ComponentRepository extends SummaryAwareRepository { " }" + " }" + "}"; - private static final String BYNAME = "function(doc) {" + + private static final String BY_NAME = "function(doc) {" + " if (doc.type == 'component') {" + " emit(doc.name, doc._id);" + " } " + "}"; - private static final String BYCOMPONENTTYPE = "function(doc) {" + + private static final String BY_COMPONENT_TYPE = "function(doc) {" + " if (doc.type == 'component') {" + " emit(doc.componentType, doc._id);" + " } " + "}"; - private static final String FULLBYNAME = "function(doc) {" + + private static final String FULL_BY_NAME = "function(doc) {" + " if (doc.type == 'component') {" + " emit(doc.name, doc._id);" + " } " + "}"; - private static final String BYLINKINGRELEASE = "function(doc) {" + + private static final String BY_LINKING_RELEASE = "function(doc) {" + " if (doc.type == 'release') {" + " for(var i in doc.releaseIdToRelationship) {" + " emit(i, doc.componentId);" + " }" + " }" + "}"; - private static final String BYFOSSOLOGYID = "function(doc) {\n" + + private static final String BY_FOSSOLOGY_ID = "function(doc) {\n" + " if (doc.type == 'release') {\n" + " if (Array.isArray(doc.externalToolProcesses)) {\n" + " for (var i = 0; i < doc.externalToolProcesses.length; i++) {\n" + @@ -97,7 +97,7 @@ public class ComponentRepository extends SummaryAwareRepository { " }\n" + " }\n" + "}"; - private static final String BYEXTERNALIDS = "function(doc) {" + + private static final String BY_EXTERNAL_IDS = "function(doc) {" + " if (doc.type == 'component') {" + " for (var externalId in doc.externalIds) {" + " try {" + @@ -115,19 +115,19 @@ public class ComponentRepository extends SummaryAwareRepository { " }" + " }" + "}"; - private static final String BYDEFAULTVENDORID = "function(doc) {" + + private static final String BY_DEFAULT_VENDOR_ID = "function(doc) {" + " if (doc.type == 'component') {" + " emit( doc.defaultVendorId , doc._id);" + " }" + "}"; - private static final String BYNAMELOWERCASE = "function(doc) {" + + private static final String BY_NAME_LOWERCASE = "function(doc) {" + " if (doc.type == 'component') {" + - " emit(doc.name.toLowerCase(), doc._id);" + + " emit(doc.name.toLowerCase().trim(), doc._id);" + " } " + "}"; - private static final String BYMAINLICENSE = "function(doc) {" + + private static final String BY_MAIN_LICENSE = "function(doc) {" + " if (doc.type == 'component') {" + " if(doc.mainLicenseIds) {" + " emit(doc.mainLicenseIds.join(), doc._id);" + @@ -137,7 +137,7 @@ public class ComponentRepository extends SummaryAwareRepository { " }" + "}"; - private static final String BYVENDOR = "function(doc) {" + + private static final String BY_VENDOR = "function(doc) {" + " if (doc.type == 'component') {" + " if(doc.vendorNames) {" + " emit(doc.vendorNames.join(), doc._id);" + @@ -151,20 +151,20 @@ public ComponentRepository(DatabaseConnectorCloudant db, ReleaseRepository relea super(Component.class, db, new ComponentSummary(releaseRepository, vendorRepository)); Map views = new HashMap(); views.put("all", createMapReduce(ALL, null)); - views.put("byCreatedOn", createMapReduce(BYCREATEDON, null)); - views.put("usedAttachmentContents", createMapReduce(USEDATTACHMENTCONTENTS, null)); - views.put("mycomponents", createMapReduce(MYCOMPONENTS, null)); + views.put("byCreatedOn", createMapReduce(BY_CREATED_ON, null)); + views.put("usedAttachmentContents", createMapReduce(USED_ATTACHMENT_CONTENTS, null)); + views.put("mycomponents", createMapReduce(MY_COMPONENTS, null)); views.put("subscribers", createMapReduce(SUBSCRIBERS, null)); - views.put("byname", createMapReduce(BYNAME, null)); - views.put("bycomponenttype", createMapReduce(BYCOMPONENTTYPE, null)); - views.put("fullbyname", createMapReduce(FULLBYNAME, null)); - views.put("byLinkingRelease", createMapReduce(BYLINKINGRELEASE, null)); - views.put("byFossologyId", createMapReduce(BYFOSSOLOGYID, null)); - views.put("byExternalIds", createMapReduce(BYEXTERNALIDS, null)); - views.put("byDefaultVendorId", createMapReduce(BYDEFAULTVENDORID, null)); - views.put("bynamelowercase", createMapReduce(BYNAMELOWERCASE, null)); - views.put("bymainlicense", createMapReduce(BYMAINLICENSE, null)); - views.put("byvendor", createMapReduce(BYVENDOR, null)); + views.put("byname", createMapReduce(BY_NAME, null)); + views.put("bycomponenttype", createMapReduce(BY_COMPONENT_TYPE, null)); + views.put("fullbyname", createMapReduce(FULL_BY_NAME, null)); + views.put("byLinkingRelease", createMapReduce(BY_LINKING_RELEASE, null)); + views.put("byFossologyId", createMapReduce(BY_FOSSOLOGY_ID, null)); + views.put("byExternalIds", createMapReduce(BY_EXTERNAL_IDS, null)); + views.put("byDefaultVendorId", createMapReduce(BY_DEFAULT_VENDOR_ID, null)); + views.put("bynamelowercase", createMapReduce(BY_NAME_LOWERCASE, null)); + views.put("bymainlicense", createMapReduce(BY_MAIN_LICENSE, null)); + views.put("byvendor", createMapReduce(BY_VENDOR, null)); initStandardDesignDocument(views, db); } @@ -215,7 +215,7 @@ public Set getComponentIdsByName(String name, boolean caseInsenstive) { return queryForIdsAsValue("byname", name); } - public List searchByNameForExport(String name, boolean caseSensitive) { + public List searchComponentByName(String name, boolean caseSensitive) { Set componentIds; if (caseSensitive) { componentIds = queryForIdsAsValueByPrefix("fullbyname", name); diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/DatabaseHandlerUtil.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/DatabaseHandlerUtil.java index b9ae179cc3..904349fdf6 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/DatabaseHandlerUtil.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/DatabaseHandlerUtil.java @@ -400,9 +400,9 @@ public static void trimStringFields(T obj, List listOfStrFields) { changeLog.setDocumentType(newProjVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } else if (newDocVersion instanceof ObligationList) { - ObligationList newProjVer = (ObligationList) newDocVersion; - changeLog.setDocumentId(newProjVer.getId()); - changeLog.setDocumentType(newProjVer.getType()); + ObligationList newOblListVer = (ObligationList) newDocVersion; + changeLog.setDocumentId(newOblListVer.getId()); + changeLog.setDocumentType(newOblListVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } else if (newDocVersion instanceof AttachmentContent) { AttachmentContent newAttachmentContentVer = (AttachmentContent) newDocVersion; @@ -412,19 +412,19 @@ public static void trimStringFields(T obj, List listOfStrFields) { info.put(AttachmentContent._Fields.FILENAME.name(), newAttachmentContentVer.getFilename()); info.put(AttachmentContent._Fields.CONTENT_TYPE.name(), newAttachmentContentVer.getContentType()); } else if (newDocVersion instanceof Component) { - Component newProjVer = (Component) newDocVersion; - changeLog.setDocumentId(newProjVer.getId()); - changeLog.setDocumentType(newProjVer.getType()); + Component newCompVer = (Component) newDocVersion; + changeLog.setDocumentId(newCompVer.getId()); + changeLog.setDocumentType(newCompVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } else if (newDocVersion instanceof Release) { - Release newProjVer = (Release) newDocVersion; - changeLog.setDocumentId(newProjVer.getId()); - changeLog.setDocumentType(newProjVer.getType()); + Release newRelVer = (Release) newDocVersion; + changeLog.setDocumentId(newRelVer.getId()); + changeLog.setDocumentType(newRelVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } else if (newDocVersion instanceof ModerationRequest) { - ModerationRequest newProjVer = (ModerationRequest) newDocVersion; - changeLog.setDocumentId(newProjVer.getId()); - changeLog.setDocumentType(newProjVer.getType()); + ModerationRequest newModReqVer = (ModerationRequest) newDocVersion; + changeLog.setDocumentId(newModReqVer.getId()); + changeLog.setDocumentType(newModReqVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } else if (newDocVersion instanceof SPDXDocument) { SPDXDocument newProjVer = (SPDXDocument) newDocVersion; @@ -442,9 +442,9 @@ public static void trimStringFields(T obj, List listOfStrFields) { changeLog.setDocumentType(newProjVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_SPDX); } else if (newDocVersion instanceof Obligation) { - Obligation newProjVer = (Obligation) newDocVersion; - changeLog.setDocumentId(newProjVer.getId()); - changeLog.setDocumentType(newProjVer.getType()); + Obligation newOblVer = (Obligation) newDocVersion; + changeLog.setDocumentId(newOblVer.getId()); + changeLog.setDocumentType(newOblVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java index 650bdfaa80..e07d382dcb 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandler.java @@ -17,11 +17,14 @@ import com.google.common.collect.*; import com.google.gson.JsonArray; import com.google.gson.JsonObject; + +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.thrift.TException; - import org.eclipse.sw360.common.utils.BackendUtils; import org.eclipse.sw360.components.summary.SummaryType; +import org.eclipse.sw360.cyclonedx.CycloneDxBOMExporter; +import org.eclipse.sw360.cyclonedx.CycloneDxBOMImporter; import org.eclipse.sw360.datahandler.businessrules.ReleaseClearingStateSummaryComputer; import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; import org.eclipse.sw360.datahandler.common.*; @@ -54,6 +57,7 @@ import java.io.*; import java.net.MalformedURLException; +import java.nio.charset.Charset; import java.time.Instant; import java.util.*; import java.util.concurrent.TimeUnit; @@ -1099,7 +1103,7 @@ public List getReleaseClearingStatusesWithAccessibili ReleaseClearingStatusData releaseClearingStatusData = new ReleaseClearingStatusData(release) .setProjectNames(joinStrings(projectNames)) .setMainlineStates(joinStrings(mainlineStates)) - .setComponentType(componentsById.get(release.getComponentId()).getComponentType()); + .setComponentType(componentsById.get(release.getComponentId()).getComponentType()); boolean isAccessible = componentDatabaseHandler.isReleaseActionAllowed(release, user, RequestedAction.READ); releaseClearingStatusData.setAccessible(isAccessible); @@ -1699,6 +1703,48 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen } } + public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception { + final AttachmentContent attachmentContent = attachmentConnector.getAttachmentContent(attachmentContentId); + final Duration timeout = Duration.durationOf(30, TimeUnit.SECONDS); + try { + final AttachmentStreamConnector attachmentStreamConnector = new AttachmentStreamConnector(timeout); + try (final InputStream inputStream = attachmentStreamConnector + .unsafeGetAttachmentStream(attachmentContent)) { + final CycloneDxBOMImporter cycloneDxBOMImporter = new CycloneDxBOMImporter(this, + componentDatabaseHandler, attachmentConnector, user); + return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user); + } + } catch (IOException e) { + log.error("Error while importing / parsing CycloneDX SBOM! ", e); + throw new SW360Exception(e.getMessage()); + } + } + + public RequestSummary exportCycloneDxSbom(String projectId, String bomType, Boolean includeSubProjReleases, User user) throws SW360Exception { + try { + final CycloneDxBOMExporter cycloneDxBOMExporter = new CycloneDxBOMExporter(this, componentDatabaseHandler, user); + return cycloneDxBOMExporter.exportSbom(projectId, bomType, includeSubProjReleases, user); + } catch (Exception e) { + log.error("Error while exporting CycloneDX SBOM! ", e); + throw new SW360Exception(e.getMessage()); + } + } + + public String getSbomImportInfoFromAttachmentAsString(String attachmentContentId) throws SW360Exception { + final AttachmentContent attachmentContent = attachmentConnector.getAttachmentContent(attachmentContentId); + final Duration timeout = Duration.durationOf(30, TimeUnit.SECONDS); + try { + final AttachmentStreamConnector attachmentStreamConnector = new AttachmentStreamConnector(timeout); + try (final InputStream inputStream = attachmentStreamConnector + .unsafeGetAttachmentStream(attachmentContent)) { + return IOUtils.toString(inputStream, Charset.defaultCharset()); + } + } catch (IOException e) { + log.error("Error while getting sbom import info from attachment! ", e); + throw new SW360Exception(e.getMessage()); + } + } + private void removeLeadingTrailingWhitespace(Project project) { DatabaseHandlerUtil.trimStringFields(project, listOfStringFieldsInProjToTrim); @@ -1781,7 +1827,7 @@ private void flattenClearingStatusForReleases(Map releaseIdToRelationship = rel.getReleaseIdToRelationship(); releaseOrigin.put(releaseId, SW360Utils.printName(rel)); @@ -1811,7 +1857,7 @@ private void flattenlinkedReleaseOfRelease(Map rele if (releaseOrigin.containsKey(releaseId)) return; Release rel = componentDatabaseHandler.getRelease(releaseId, user); - + if (!isInaccessibleLinkMasked || componentDatabaseHandler.isReleaseActionAllowed(rel, user, RequestedAction.READ)) { Map subReleaseIdToRelationship = rel.getReleaseIdToRelationship(); releaseOrigin.put(releaseId, SW360Utils.printName(rel)); @@ -1873,7 +1919,7 @@ private Map createReleaseCSRow(String relation, String projectMa clearingStatusList.add(row); return row; } - + private Map createInaccessibleReleaseCSRow(List> clearingStatusList) throws SW360Exception { Map row = new HashMap<>(); row.put("id", ""); diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java index 8b7064fbfc..aee6803f4b 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ReleaseRepository.java @@ -43,8 +43,8 @@ public class ReleaseRepository extends SummaryAwareRepository { private static final String ALL = "function(doc) { if (doc.type == 'release') emit(null, doc._id) }"; - private static final String BYNAME = "function(doc) { if(doc.type == 'release') { emit(doc.name, doc._id) } }"; - private static final String BYCREATEDON = "function(doc) { if(doc.type == 'release') { emit(doc.createdOn, doc._id) } }"; + private static final String BY_NAME = "function(doc) { if(doc.type == 'release') { emit(doc.name, doc._id) } }"; + private static final String BY_CREATED_ON = "function(doc) { if(doc.type == 'release') { emit(doc.createdOn, doc._id) } }"; private static final String SUBSCRIBERS = "function(doc) {" + " if (doc.type == 'release'){" + " for(var i in doc.subscribers) {" + @@ -52,38 +52,38 @@ public class ReleaseRepository extends SummaryAwareRepository { " }" + " }" + "}"; - private static final String USEDINRELEASERELATION = "function(doc) {" + + private static final String USED_IN_RELEASE_RELATION = "function(doc) {" + " if(doc.type == 'release') {" + " for(var id in doc.releaseIdToRelationship) {" + " emit(id, doc._id);" + " }" + " }" + "}"; - private static final String RELEASEBYVENDORID = "function(doc) {" + + private static final String RELEASE_BY_VENDOR_ID = "function(doc) {" + " if (doc.type == 'release'){" + " emit(doc.vendorId, doc._id);" + " }" + "}"; - private static final String RELEASESBYCOMPONENTID = "function(doc) {" + + private static final String RELEASES_BY_COMPONENT_ID = "function(doc) {" + " if (doc.type == 'release'){" + " emit(doc.componentId, doc._id);" + " }" + "}"; - private static final String RELEASEIDSBYVENDORID = "function(doc) {" + + private static final String RELEASE_IDS_BY_VENDOR_ID = "function(doc) {" + " if (doc.type == 'release'){" + " emit(doc.vendorId, doc._id);" + " }" + "}"; - private static final String RELEASEIDSBYLICENSEID = "function(doc) {" + + private static final String RELEASE_IDS_BY_LICENSE_ID = "function(doc) {" + " if (doc.type == 'release'){" + " for(var i in doc.mainLicenseIds) {" + " emit(doc.mainLicenseIds[i], doc._id);" + " }" + " }" + "}"; - private static final String BYEXTERNALIDS = "function(doc) {" + + private static final String BY_EXTERNAL_IDS = "function(doc) {" + " if (doc.type == 'release') {" + " for (var externalId in doc.externalIds) {" + " try {" + @@ -171,19 +171,19 @@ public ReleaseRepository(DatabaseConnectorCloudant db, VendorRepository vendorRe super(Release.class, db, new ReleaseSummary(vendorRepository)); Map views = new HashMap(); views.put("all", createMapReduce(ALL, null)); - views.put("byname", createMapReduce(BYNAME, null)); - views.put("byCreatedOn", createMapReduce(BYCREATEDON, null)); + views.put("byname", createMapReduce(BY_NAME, null)); + views.put("byCreatedOn", createMapReduce(BY_CREATED_ON, null)); views.put("subscribers", createMapReduce(SUBSCRIBERS, null)); - views.put("usedInReleaseRelation", createMapReduce(USEDINRELEASERELATION, null)); - views.put("releaseByVendorId", createMapReduce(RELEASEBYVENDORID, null)); - views.put("releasesByComponentId", createMapReduce(RELEASESBYCOMPONENTID, null)); - views.put("releaseIdsByLicenseId", createMapReduce(RELEASEIDSBYLICENSEID, null)); - views.put("byExternalIds", createMapReduce(BYEXTERNALIDS, null)); + views.put("usedInReleaseRelation", createMapReduce(USED_IN_RELEASE_RELATION, null)); + views.put("releaseByVendorId", createMapReduce(RELEASE_BY_VENDOR_ID, null)); + views.put("releasesByComponentId", createMapReduce(RELEASES_BY_COMPONENT_ID, null)); + views.put("releaseIdsByLicenseId", createMapReduce(RELEASE_IDS_BY_LICENSE_ID, null)); + views.put("byExternalIds", createMapReduce(BY_EXTERNAL_IDS, null)); views.put("releaseByCpeId", createMapReduce(BY_LOWERCASE_RELEASE_CPE_VIEW, null)); views.put("releaseBySvmId", createMapReduce(RELEASES_BY_SVM_ID_RELEASE_VIEW, null)); views.put("releaseByName", createMapReduce(BY_LOWERCASE_RELEASE_NAME_VIEW, null)); views.put("releaseByVersion", createMapReduce(BY_LOWERCASE_RELEASE_VERSION_VIEW, null)); - views.put("releaseIdsByVendorId", createMapReduce(RELEASEIDSBYVENDORID, null)); + views.put("releaseIdsByVendorId", createMapReduce(RELEASE_IDS_BY_VENDOR_ID, null)); views.put("byStatus", createMapReduce(BY_STATUS_VIEW, null)); views.put("byECCAssessorContactPerson", createMapReduce(BY_ASSESSOR_CONTACT_PERSON_VIEW, null)); views.put("byECCAssessorGroup", createMapReduce(BY_ASSESSOR_DEPARTMENT_VIEW, null)); @@ -196,10 +196,15 @@ public List searchByNamePrefix(String name) { return makeSummary(SummaryType.SHORT, queryForIdsByPrefix("byname", name)); } - public List searchByNameAndVersion(String name, String version){ - List releasesMatchingName = new ArrayList(getFullDocsById(queryForIdsAsValue("byname", name))); + public List searchByNameAndVersion(String name, String version, boolean caseInsenstive){ + List releasesMatchingName; + if (caseInsenstive) { + releasesMatchingName = new ArrayList(queryView("releaseByName", name.toLowerCase())); + } else { + releasesMatchingName = new ArrayList(queryView("byname", name)); + } List releasesMatchingNameAndVersion = releasesMatchingName.stream() - .filter(r -> isNullOrEmpty(version) ? isNullOrEmpty(r.getVersion()) : version.equals(r.getVersion())) + .filter(r -> isNullOrEmpty(version) ? isNullOrEmpty(r.getVersion()) : version.equalsIgnoreCase(r.getVersion())) .collect(Collectors.toList()); return releasesMatchingNameAndVersion; } diff --git a/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java b/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java index 4ef086eecd..1c3499fd9e 100644 --- a/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java +++ b/backend/src/src-projects/src/main/java/org/eclipse/sw360/projects/ProjectHandler.java @@ -20,8 +20,8 @@ import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.RequestStatus; -import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.RequestSummary; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.attachments.Attachment; import org.eclipse.sw360.datahandler.thrift.components.ReleaseClearingStatusData; import org.eclipse.sw360.datahandler.thrift.projects.ClearingRequest; @@ -30,7 +30,6 @@ import org.eclipse.sw360.datahandler.thrift.projects.ProjectData; import org.eclipse.sw360.datahandler.thrift.projects.ProjectLink; import org.eclipse.sw360.datahandler.thrift.projects.ObligationList; -import org.eclipse.sw360.datahandler.thrift.projects.ProjectRelationship; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.projects.UsedReleaseRelations; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -39,11 +38,11 @@ import com.cloudant.client.api.CloudantClient; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.*; import java.util.function.Supplier; import static org.eclipse.sw360.datahandler.common.SW360Assert.*; -import java.nio.ByteBuffer; /** * Implementation of the Thrift service @@ -237,6 +236,26 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen return handler.importBomFromAttachmentContent(user, attachmentContentId); } + @Override + public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception { + assertId(attachmentContentId); + assertUser(user); + return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId); + } + + @Override + public RequestSummary exportCycloneDxSbom(String projectId, String bomType, boolean includeSubProjReleases, User user) throws SW360Exception { + assertId(projectId); + assertUser(user); + return handler.exportCycloneDxSbom(projectId, bomType, includeSubProjReleases, user); + } + + @Override + public String getSbomImportInfoFromAttachmentAsString(String attachmentContentId) throws SW360Exception { + assertId(attachmentContentId); + return handler.getSbomImportInfoFromAttachmentAsString(attachmentContentId); + } + //////////////////////////// // ADD INDIVIDUAL OBJECTS // //////////////////////////// diff --git a/frontend/sw360-portlet/pom.xml b/frontend/sw360-portlet/pom.xml index 47efd557f4..61459ff90b 100644 --- a/frontend/sw360-portlet/pom.xml +++ b/frontend/sw360-portlet/pom.xml @@ -216,6 +216,10 @@ jstl-api 1.2 + + com.github.package-url + packageurl-java + com.google.guava guava diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java index f0065adce7..19f6604969 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java @@ -26,6 +26,7 @@ * @author stefan.jaeger@evosoft.com * @author alex.borodin@evosoft.com */ + public class PortalConstants { public static final String PROPERTIES_FILE_PATH = "/sw360.properties"; @@ -277,6 +278,7 @@ public class PortalConstants { public static final String RELEASE_SEARCH_BY_VENDOR = "releaseSearchByVendor"; public static final String OBLIGATION_ELEMENT_SEARCH = "obligationElementSearch"; public static final String OBLIGATION_ELEMENT_ID = "obligationElementId"; + public static final String LOAD_SBOM_IMPORT_INFO = "loadSbomImportInfo"; public static final String RELEASE_LIST_FROM_LINKED_PROJECTS = "releaseListFromLinkedProjects"; public static final String STATE; @@ -552,12 +554,16 @@ public class PortalConstants { public static final String PARENT_BRANCH_ID = "parent_branch_id"; public static final String PARENT_SCOPE_GROUP_ID = "parentScopeGroupId"; - // bom import + // bom import / export public static final String PREPARE_IMPORT_BOM = "prepareImportBom"; public static final String IMPORT_BOM = "importBom"; public static final String IMPORT_BOM_AS_NEW = "importBomAsNew"; public static final String NEW_RELEASE_VERSION = "newReleaseVersion"; public static final String RDF_FILE_PATH = "rdfFilePath"; + public static final String BOM_TYPE = "bomType"; + public static final String EXPORT_SBOM = "exportSbom"; + public static final String SBOM_FROMAT = "sbomFormat"; + public static final String IS_SBOM_IMPORT_EXPORT_ACCESS_USER = "isSbomImportExportAccessUser"; // project actions public static final String VIEW_LINKED_PROJECTS = "view_linked_projects"; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java index 7c99ffd0f2..5965790063 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java @@ -150,6 +150,10 @@ public static ObligationType getObligationTypeFromString(String enumNumber) { return ObligationType.findByValue(parseInt(enumNumber)); } + public static CycloneDxComponentType getCycloneDxComponentTypeFromString(String enumNumber) { + return CycloneDxComponentType.findByValue(parseInt(enumNumber)); + } + public static > void setFieldValue(PortletRequest request, T instance, U field, FieldMetaData fieldMetaData, String prefix) { String value = request.getParameter(prefix + field.toString()); @@ -205,6 +209,8 @@ else if (field == Obligation._Fields.OBLIGATION_LEVEL) return getObligationLevelFromString(value); else if (field == Obligation._Fields.OBLIGATION_TYPE) return getObligationTypeFromString(value); + else if (field == Component._Fields.CDX_COMPONENT_TYPE) + return getCycloneDxComponentTypeFromString(value); else { LOGGER.error("Missing case in enumFromString, unknown field was " + field.toString()); return null; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java index 68ecb78252..c0a70119b7 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java @@ -25,6 +25,7 @@ import com.liferay.portal.kernel.servlet.SessionMessages; import com.liferay.portal.kernel.servlet.SessionErrors; import com.liferay.portal.kernel.theme.ThemeDisplay; +import com.liferay.portal.kernel.util.ContentTypes; import com.liferay.portal.kernel.util.PortalUtil; import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.kernel.util.ResourceBundleUtil; @@ -238,6 +239,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th removeOrphanObligation(request, response); } else if (PortalConstants.IMPORT_BOM.equals(action)) { importBom(request, response); + } else if (PortalConstants.EXPORT_SBOM.equals(action)) { + exportSbom(request, response); } else if (PortalConstants.CREATE_CLEARING_REQUEST.equals(action)) { createClearingRequest(request, response); } else if (PortalConstants.VIEW_CLEARING_REQUEST.equals(action)) { @@ -252,6 +255,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th addLicenseToLinkedReleases(request, response); } else if (PortalConstants.LOAD_SPDX_LICENSE_INFO.equals(action)) { loadSpdxLicenseInfo(request, response); + } else if (PortalConstants.LOAD_SBOM_IMPORT_INFO.equals(action)) { + loadSbomImportInfoFromAttachment(request, response); } else if (isGenericAction(action)) { dealWithGenericAction(request, response, action); } else if (PortalConstants.LOAD_CHANGE_LOGS.equals(action) || PortalConstants.VIEW_CHANGE_LOGS.equals(action)) { @@ -430,13 +435,24 @@ private void removeOrphanObligation(ResourceRequest request, ResourceResponse re } private void importBom(ResourceRequest request, ResourceResponse response) { - ProjectService.Iface projectClient = thriftClients.makeProjectClient(); User user = UserCacheHolder.getUserFromRequest(request); - String attachmentContentId = request.getParameter(ATTACHMENT_CONTENT_ID); + ResourceParameters parameters = request.getResourceParameters(); + String attachmentContentId = parameters.getValue(ATTACHMENT_CONTENT_ID); + String bomtype = parameters.getValue(BOM_TYPE); + String projectId = parameters.getValue(PROJECT_ID); + final RequestSummary requestSummary; try { - final RequestSummary requestSummary = projectClient.importBomFromAttachmentContent(user, attachmentContentId); + boolean isSPDX = "SPDX".equals(bomtype); + ProjectService.Iface projectClient = thriftClients.makeProjectClient(); + if (isSPDX) { + requestSummary = projectClient.importBomFromAttachmentContent(user, attachmentContentId); + } else { + Locale locale = request.getLocale(); + requestSummary = projectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId); + } + boolean isSuccess = RequestStatus.SUCCESS.equals(requestSummary.getRequestStatus()); String portletId = (String) request.getAttribute(WebKeys.PORTLET_ID); ThemeDisplay tD = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); long plid = tD.getPlid(); @@ -444,12 +460,29 @@ private void importBom(ResourceRequest request, ResourceResponse response) { LiferayPortletURL projectUrl = PortletURLFactoryUtil.create(request, portletId, plid, PortletRequest.RENDER_PHASE); projectUrl.setParameter(PortalConstants.PAGENAME, PortalConstants.PAGENAME_DETAIL); - projectUrl.setParameter(PortalConstants.PROJECT_ID, requestSummary.getMessage()); - JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); - jsonObject.put("redirectUrl", projectUrl.toString()); + if (isSPDX) { + projectUrl.setParameter(PortalConstants.PROJECT_ID, requestSummary.getMessage()); + jsonObject.put("redirectUrl", projectUrl.toString()); + } else { + try { + if (CommonUtils.isNotNullEmptyOrWhitespace(requestSummary.getMessage())) { + jsonObject = JSONFactoryUtil.createJSONObject(requestSummary.getMessage()); + requestSummary.unsetMessage(); + } + if (isSuccess || RequestStatus.DUPLICATE.equals(requestSummary.getRequestStatus())) { + projectUrl.setParameter(PortalConstants.PROJECT_ID, jsonObject.getString("projectId")); + jsonObject.put("redirectUrl", projectUrl.toString()); + } + } catch (JSONException e) { + log.error("JSON Exception occured, maybe the 'RequestSummary' message is not in JSON form: " + e.getMessage()); + } + } + if (!isSuccess) { + attachmentPortletUtils.deleteAttachments(Sets.newHashSet(attachmentContentId)); + } renderRequestSummary(request, response, requestSummary, jsonObject); } catch (TException e) { log.error("Failed to import BOM.", e); @@ -457,6 +490,44 @@ private void importBom(ResourceRequest request, ResourceResponse response) { } } + private void exportSbom(ResourceRequest request, ResourceResponse response) { + final User user = UserCacheHolder.getUserFromRequest(request); + final ResourceParameters parameters = request.getResourceParameters(); + final String bomtype = parameters.getValue(SBOM_FROMAT); + final String projectId = parameters.getValue(PROJECT_ID); + final Boolean includeSubProjReleases = Boolean.valueOf(CommonUtils.nullToEmptyString(parameters.getValue(PROJECT_WITH_SUBPROJECT))); + final String fileName = EXPORT_SBOM; + String contentType = ContentTypes.APPLICATION_JSON; + String bomString = ""; + + try { + if (CommonUtils.isNotNullEmptyOrWhitespace(projectId)) { + ProjectService.Iface projectClient = thriftClients.makeProjectClient(); + RequestSummary summary = projectClient.exportCycloneDxSbom(projectId, bomtype, includeSubProjReleases, user); + RequestStatus staus = summary.getRequestStatus(); + if (RequestStatus.FAILED_SANITY_CHECK.equals(staus)) { + bomString = "{\"status\": \"" + staus.name() + "\"}"; + } else if (RequestStatus.ACCESS_DENIED.equals(staus)) { + bomString = "{\"status\": \"" + staus.name() + "\", \"message\": \"" + SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE + "\"}"; + } else if (RequestStatus.FAILURE.equals(staus)) { + bomString = "{\"status\": \"" + staus.name() + "\", \"message\": \"" + summary.getMessage() + "\"}"; + } else { + bomString = summary.getMessage(); + if (SW360Constants.XML_FILE_EXTENSION.equalsIgnoreCase(bomtype)) { + contentType = ContentTypes.TEXT_XML; + } + } + } + PortletResponseUtil.sendFile(request, response, fileName, bomString.getBytes(), contentType); + } catch (TException e) { + log.error("An error occured while generating SBOM file for export.", e); + response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } catch (IOException e) { + log.error("Failed to export SBOM file.", e); + response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } + } + private void createClearingRequest(ResourceRequest request, ResourceResponse response) throws PortletException { User user = UserCacheHolder.getUserFromRequest(request); ClearingRequest clearingRequest = null; @@ -1484,6 +1555,7 @@ private void prepareStandardView(RenderRequest request) throws IOException { request.setAttribute(CUSTOM_FIELD_PREFERRED_CLEARING_DATE_LIMIT, dateLimit); Integer criticalCount = modClient.getOpenCriticalCrCountByGroup(user.getDepartment()); request.setAttribute(CRITICAL_CR_COUNT, criticalCount); + request.setAttribute(PortalConstants.IS_SBOM_IMPORT_EXPORT_ACCESS_USER, SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)); } catch(TException e) { log.error("Error in getting the projectList from backend ", e); } @@ -1623,6 +1695,7 @@ private void prepareDetailView(RenderRequest request, RenderResponse response) t total_vuls.addAll(vuls); } } + request.setAttribute(PortalConstants.IS_SBOM_IMPORT_EXPORT_ACCESS_USER, SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)); request.setAttribute(PortalConstants.TOTAL_VULNERABILITY_COUNT, total_vuls.size()); putDirectlyLinkedReleasesInRequest(request, project); Set usingProjects = client.searchLinkingProjects(id, user); @@ -2363,6 +2436,7 @@ private void prepareProjectDuplicate(RenderRequest request) { request.setAttribute(USING_PROJECTS, Collections.emptySet()); request.setAttribute(ALL_USING_PROJECTS_COUNT, 0); request.setAttribute(SOURCE_PROJECT_ID, id); + request.setAttribute(PortalConstants.IS_SBOM_IMPORT_EXPORT_ACCESS_USER, SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)); } else { Project project = new Project(); project.setBusinessUnit(user.getDepartment()); @@ -2898,6 +2972,22 @@ private void addLicenseToLinkedReleases(ResourceRequest request, ResourceRespons } } + private void loadSbomImportInfoFromAttachment(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + String attachmentContentId = request.getResourceParameters().getValue(ATTACHMENT_CONTENT_ID); + final ProjectService.Iface client = thriftClients.makeProjectClient(); + try { + String importInfo = client.getSbomImportInfoFromAttachmentAsString(attachmentContentId); + JSONObject jsonObject = JSONFactoryUtil.createJSONObject(importInfo); + writeJSON(request, response, jsonObject); + } catch (SW360Exception e) { + log.error("Error fetching sbom import info from attachment db: " + attachmentContentId, e); + } catch (TException e) { + log.error("Error fetching sbom import info from attachment db: " + attachmentContentId, e); + } catch (JSONException e) { + log.error("An exception occured while parsing sbom import info: " + attachmentContentId, e); + } + } + private void loadSpdxLicenseInfo(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { final User user = UserCacheHolder.getUserFromRequest(request); final String releaseId = request.getParameter(PortalConstants.RELEASE_ID); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_tooltips.scss b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_tooltips.scss index 1a5bf6fa4d..0bb3160098 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_tooltips.scss +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_tooltips.scss @@ -117,6 +117,43 @@ display: none; } +/** CycloneDxComponentType **/ +.sw360-tt-CycloneDxComponentType:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-APPLICATION:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-CONTAINER:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-DEVICE:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-FILE:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-FIRMWARE:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-FRAMEWORK:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-LIBRARY:hover:after { + content: attr(data-content); +} + +.sw360-tt-CycloneDxComponentType-OPERATING_SYSTEM:hover:after { + content: attr(data-content); +} + /** Ternary **/ .sw360-tt-Ternary:hover:after { content: attr(data-content); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/oauthclient/view.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/oauthclient/view.jsp index 5ca89952cd..0417d74caf 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/oauthclient/view.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/oauthclient/view.jsp @@ -377,7 +377,7 @@ $('#copyToClipboard').on('click', function(event) { let textSelector = "#confirmDialog table tr td:eq(1)", textToCopy = $("#confirmDialog table tr td pre#token").text(); - clipboard.copyToClipboard(textToCopy, textSelector, true); + clipboard.copyToClipboard(textToCopy, '#copyToClipboard'); }); clientsTbl.destroy(); loadListOfClient(); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/detailOverview.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/detailOverview.jspf index abf85f21cd..109e0d2b29 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/detailOverview.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/detailOverview.jspf @@ -212,7 +212,7 @@ $('#copyToClipboard').on('click', function(event) { let textSelector = "table tr td#documentId", textToCopy = $(textSelector).clone().children().remove().end().text().trim(); - clipboard.copyToClipboard(textToCopy, textSelector); + clipboard.copyToClipboard(textToCopy, '#copyToClipboard'); }); let keyComponent, valueComponent; diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/editBasicInfo.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/editBasicInfo.jspf index 82b5f5f59b..e380f14bb1 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/editBasicInfo.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/components/editBasicInfo.jspf @@ -8,6 +8,7 @@ ~ SPDX-License-Identifier: EPL-2.0 --%> +<%@ page import="org.eclipse.sw360.datahandler.thrift.CycloneDxComponentType" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.components.ComponentType" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.components.Component" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.Visibility" %> @@ -64,9 +65,24 @@
+ +
+ + + + + + +
+ + +
@@ -74,8 +90,6 @@ value="" placeholder=""/>
- -
@@ -90,13 +104,6 @@ placeholder="" value=""/>
- -
- - " value=""/> -
- @@ -130,6 +137,13 @@ + +
+ + " value=""/> +
+
@@ -137,14 +151,6 @@ placeholder="">
- -
- - " - value="" readonly class="form-control"/> -
-
: + + : + + : diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/detailOverview.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/detailOverview.jspf index b8e73083c9..4e1268cb72 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/detailOverview.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/detailOverview.jspf @@ -251,7 +251,7 @@ $('#copyToClipboard').on('click', function(event) { let textSelector = "table tr td#documentId", textToCopy = $(textSelector).clone().children().remove().end().text().trim(); - clipboard.copyToClipboard(textToCopy, textSelector); + clipboard.copyToClipboard(textToCopy, '#copyToClipboard'); }); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/view.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/view.jsp index 3201acf65b..5118fedf26 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/view.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/view.jsp @@ -184,7 +184,7 @@