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/components/summary/ReleaseSummary.java b/backend/src-common/src/main/java/org/eclipse/sw360/components/summary/ReleaseSummary.java index 83366ad798..c944c2fcbb 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/components/summary/ReleaseSummary.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/components/summary/ReleaseSummary.java @@ -109,6 +109,7 @@ private void setShortSummaryFields(Release document, Release copy) { copyField(document, copy, _Fields.RELEASE_DATE); copyField(document, copy, _Fields.SOURCE_CODE_DOWNLOADURL); copyField(document, copy, _Fields.BINARY_DOWNLOADURL); + copyField(document, copy, _Fields.PACKAGE_IDS); } private void setAdditionalFieldsForSummariesOtherThanShortAndDetailedExport(Release document, Release copy){ 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..fa8479f9a9 --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMImporter.java @@ -0,0 +1,870 @@ +/* + * 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.AbstractMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +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.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.PackageDatabaseHandler; +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.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.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageManagerType; +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 SCHEMA_PATTERN = ".+://(\\w*(?:[\\-@.\\\\s,_:/][/(.\\-)A-Za-z0-9]+)*)"; + private static final String DOT_GIT = ".git"; + private static final String SLASH = "/"; + private static final String DOT = "."; + private static final String HASH = "#"; + private static final String JOINER = "||"; + private static final String XML_FILE_EXTENSION = "xml"; + private static final String JSON_FILE_EXTENSION = "json"; + private static final Pattern THIRD_SLASH_PATTERN = Pattern.compile("[^/]*(/[^/]*){2}"); + private static final Pattern FIRST_SLASH_PATTERN = Pattern.compile("/(.*)"); + private static final String VCS_HTTP_REGEX = "^[^:]+://"; + private static final String CLEAN_PUBLISHER_REGEX = "<[^>]+>"; + private static final String HTTPS_SCHEME = "https://"; + 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 PKG_CREATION_COUNT_KEY = "pkgCreationCount"; + private static final String PKG_REUSE_COUNT_KEY = "pkgReuseCount"; + private static final String DUPLICATE_COMPONENT = "dupComp"; + private static final String DUPLICATE_RELEASE = "dupRel"; + private static final String DUPLICATE_PACKAGE = "dupPkg"; + private static final String INVALID_COMPONENT = "invalidComp"; + private static final String INVALID_RELEASE = "invalidRel"; + private static final String INVALID_PACKAGE = "invalidPkg"; + private static final String DEFAULT_CATEGORY = "Default_Category"; + private static final String PROJECT_ID = "projectId"; + private static final String PROJECT_NAME = "projectName"; + private static final boolean IS_PACKAGE_PORTLET_ENABLED = SW360Constants.IS_PACKAGE_PORTLET_ENABLED; + private static final Predicate typeFilter = type -> ExternalReference.Type.VCS.equals(type); + + private final ProjectDatabaseHandler projectDatabaseHandler; + private final ComponentDatabaseHandler componentDatabaseHandler; + private final PackageDatabaseHandler packageDatabaseHandler; + private final User user; + private final AttachmentConnector attachmentConnector; + + public CycloneDxBOMImporter(ProjectDatabaseHandler projectDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler, + PackageDatabaseHandler packageDatabaseHandler, AttachmentConnector attachmentConnector, User user) { + this.projectDatabaseHandler = projectDatabaseHandler; + this.componentDatabaseHandler = componentDatabaseHandler; + this.packageDatabaseHandler = packageDatabaseHandler; + this.attachmentConnector = attachmentConnector; + this.user = user; + } + + /** + * Creating the Map of Sanitized VCS URLs to List of Component -> Grouping by VCS URLs + * Sanitizing VCS URLs: + * git+https://github.com/microsoft/ApplicationInsights-JS.git/tree/master/shared/AppInsightsCommon + * --> microsoft.applicationinsights-js + * @param components + * @return Map> + */ + private Map> getVcsToComponentMap(List components) { + return components.stream().filter(Objects::nonNull) + .flatMap(comp -> CommonUtils.nullToEmptyList(comp.getExternalReferences()).stream() + .filter(Objects::nonNull) + .filter(ref -> ExternalReference.Type.VCS.equals(ref.getType())) + .map(ExternalReference::getUrl) + .map(String::toLowerCase) + .map(url -> url.replaceAll(SCHEMA_PATTERN, "$1")) + .map(url -> THIRD_SLASH_PATTERN.matcher(url)) + .filter(matcher -> matcher.find()) + .map(matcher -> matcher.group()) + .map(url -> FIRST_SLASH_PATTERN.matcher(url)) + .filter(matcher -> matcher.find()) + .map(matcher -> matcher.group(1)) + .map(url -> StringUtils.substringBefore(url, HASH)) + .map(url -> StringUtils.removeEnd(url, DOT_GIT)) + .map(url -> url.replaceAll(SLASH, DOT)) + .map(url -> new AbstractMap.SimpleEntry<>(url, comp))) + .collect(Collectors.groupingBy(e -> e.getKey(), + Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); + } + + @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 (fileExtension.equalsIgnoreCase(XML_FILE_EXTENSION)) { + parser = new XmlParser(); + } else if (fileExtension.equalsIgnoreCase(JSON_FILE_EXTENSION)) { + parser = new JsonParser(); + } else { + requestSummary.setMessage(String.format("Invalid file format <%s>. Only XML & JSON CycloneDX SBOM are supported!", 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()); + + long vcsCount = components.stream().map(org.cyclonedx.model.Component::getExternalReferences) + .filter(Objects::nonNull).flatMap(List::stream).map(ExternalReference::getType).filter(typeFilter).count(); + long componentsCount = components.size(); + org.cyclonedx.model.Component compMetadata = bomMetadata.getComponent(); + Map> vcsToComponentMap = new HashMap<>(); + + if (!IS_PACKAGE_PORTLET_ENABLED) { + vcsToComponentMap.put("", components); + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + } else { + + vcsToComponentMap = getVcsToComponentMap(components); + if (componentsCount == vcsCount) { + + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + } else if (componentsCount > vcsCount) { + + requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent); + + if (requestSummary.requestStatus.equals(RequestStatus.SUCCESS)) { + + String jsonMessage = requestSummary.getMessage(); + messageMap = new Gson().fromJson(jsonMessage, Map.class); + String projId = messageMap.get("projectId"); + + if (CommonUtils.isNullEmptyOrWhitespace(projId)) { + return requestSummary; + } + final Set duplicatePackages = new HashSet<>(); + final Set componentsWithoutVcs = new HashSet<>(); + final Set invalidPackages = new HashSet<>(); + + Integer relReuseCount = Integer.valueOf(messageMap.get(REL_REUSE_COUNT_KEY)); + Integer pkgReuseCount = Integer.valueOf(messageMap.get(PKG_REUSE_COUNT_KEY)); + Integer pkgCreationCount = Integer.valueOf(messageMap.get(PKG_CREATION_COUNT_KEY)); + + String packages = messageMap.get(DUPLICATE_PACKAGE); + if (CommonUtils.isNotNullEmptyOrWhitespace(packages)) { + duplicatePackages.addAll(Arrays.asList(packages.split(JOINER))); + packages = ""; + } + packages = messageMap.get(INVALID_PACKAGE); + if (CommonUtils.isNotNullEmptyOrWhitespace(packages)) { + invalidPackages.addAll(Arrays.asList(packages.split(JOINER))); + packages = ""; + } + Project project = projectDatabaseHandler.getProjectById(projId, user); + + for (org.cyclonedx.model.Component comp : components) { + if (CommonUtils.isNullOrEmptyCollection(comp.getExternalReferences()) + || comp.getExternalReferences().stream().map(ExternalReference::getType).filter(typeFilter).count() == 0) { + + final var fullName = SW360Utils.getVersionedName(comp.getName(), comp.getVersion()); + final var licenses = getLicenseFromBomComponent(comp); + final Package pkg = createPackage(comp, null, licenses); + + if (pkg == null || CommonUtils.isNullEmptyOrWhitespace(pkg.getName()) || CommonUtils.isNullEmptyOrWhitespace(pkg.getVersion()) + || CommonUtils.isNullEmptyOrWhitespace(pkg.getPurl())) { + invalidPackages.add(fullName); + log.error(String.format("Invalid package '%s' found in SBoM, missing name or version or purl! ", fullName)); + continue; + } + + try { + AddDocumentRequestSummary pkgAddSummary = packageDatabaseHandler.addPackage(pkg, user); + componentsWithoutVcs.add(fullName); + + if (CommonUtils.isNotNullEmptyOrWhitespace(pkgAddSummary.getId())) { + project.addToPackageIds(pkgAddSummary.getId()); + pkg.setId(pkgAddSummary.getId()); + if (AddDocumentRequestStatus.DUPLICATE.equals(pkgAddSummary.getRequestStatus())) { + pkgReuseCount++; + Package dupPkg = packageDatabaseHandler.getPackageById(pkgAddSummary.getId()); + if (CommonUtils.isNotNullEmptyOrWhitespace(dupPkg.getReleaseId())) { + if (!CommonUtils.nullToEmptyMap(project.getReleaseIdToUsage()).containsKey(pkgAddSummary.getId())) { + project.putToReleaseIdToUsage(pkgAddSummary.getId(), getDefaultRelation()); + } + relReuseCount++; + } + } else { + pkgCreationCount++; + } + } else { + // in case of more than 1 duplicate found, then return and show error message in UI. + log.warn("found multiple packages: " + fullName); + duplicatePackages.add(fullName); + continue; + } + } catch (SW360Exception e) { + log.error("An error occured while creating/adding package from SBOM: " + e.getMessage()); + continue; + } + + } + } + RequestStatus updateStatus = projectDatabaseHandler.updateProject(project, user); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("linking packages to project successfull: " + projId); + } + // all components does not have VCS, so return & show appropriate error in UI + messageMap.put(INVALID_COMPONENT, String.join(JOINER, componentsWithoutVcs)); + messageMap.put(INVALID_PACKAGE, String.join(JOINER, invalidPackages)); + messageMap.put(DUPLICATE_PACKAGE, String.join(JOINER, duplicatePackages)); + messageMap.put(SW360Constants.MESSAGE, + String.format("VCS information is missing for %s / %s Components!", + componentsCount - vcsCount, componentsCount)); + messageMap.put(REL_REUSE_COUNT_KEY, String.valueOf(relReuseCount)); + messageMap.put(PKG_CREATION_COUNT_KEY, String.valueOf(pkgCreationCount)); + messageMap.put(PKG_REUSE_COUNT_KEY, String.valueOf(pkgReuseCount)); + requestSummary.setMessage(convertCollectionToJSONString(messageMap)); + } + } else { + // this case is not possible, so return & show appropriate error in UI + requestSummary.setMessage(String.format(String.format( + "SBOM import aborted with error: Multiple vcs information found in compnents, vcs found: %s and total components: %s", + vcsCount, componentsCount))); + return requestSummary; + } + } + + 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(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); + 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.error("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())); + messageMap.put(SW360Constants.MESSAGE, "A project with same name and version already exists in SW360, Please import this SBOM from project details page!"); + 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; + } + + if (IS_PACKAGE_PORTLET_ENABLED) { + messageMap = importAllComponentsAsPackages(vcsToComponentMap, project); + } else { + 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<>(); + 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 rel = new Release(); + Set licenses = getLicenseFromBomComponent(bomComp); + rel = createRelease(bomComp, comp, licenses); + if (CommonUtils.isNullEmptyOrWhitespace(rel.getVersion()) ) { + log.error("release version is not present in SBoM for component: " + comp.getName()); + invalidReleases.add(comp.getName()); + continue; + } + relName = SW360Utils.getVersionedName(rel.getName(), rel.getVersion()); + + try { + AddDocumentRequestSummary relAddSummary = componentDatabaseHandler.addRelease(rel, user); + if (CommonUtils.isNotNullEmptyOrWhitespace(relAddSummary.getId())) { + rel.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; + } + project.putToReleaseIdToUsage(rel.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()) { + Set categories = new HashSet<>(comp.getCategories()); + categories.add(bomComp.getType().getTypeName()); + categories.remove(DEFAULT_CATEGORY); + comp.setCategories(categories); + } + StringBuilder description = new StringBuilder(); + if (CommonUtils.isNullEmptyOrWhitespace(comp.getDescription())) { + description.append(CommonUtils.nullToEmptyString(bomComp.getDescription()).trim()); + } else { + description.append(" | ").append(CommonUtils.nullToEmptyString(bomComp.getDescription()).trim()); + } + if (comp.isSetMainLicenseIds()) { + comp.getMainLicenseIds().addAll(licenses); + } else { + comp.setMainLicenseIds(licenses); + } + 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; + } + } + + 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 Map importAllComponentsAsPackages(Map> vcsToComponentMap, Project project) { + + final var countMap = new HashMap(); + final Set duplicateComponents = new HashSet<>(); + final Set duplicateReleases = new HashSet<>(); + final Set duplicatePackages = new HashSet<>(); + final Set invalidReleases = new HashSet<>(); + final Set invalidPackages = new HashSet<>(); + final Map releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage(); + countMap.put(REL_CREATION_COUNT_KEY, 0); countMap.put(REL_REUSE_COUNT_KEY, 0); + countMap.put(PKG_CREATION_COUNT_KEY, 0); countMap.put(PKG_REUSE_COUNT_KEY, 0); + int relCreationCount = 0, relReuseCount = 0, pkgCreationCount = 0, pkgReuseCount = 0; + + for (Map.Entry> entry : vcsToComponentMap.entrySet()) { + Component comp = createComponent(entry.getKey()); + Release rel = new Release(); + String relName = ""; + StringBuilder description = new StringBuilder(); + AddDocumentRequestSummary compAddSummary; + try { + compAddSummary = componentDatabaseHandler.addComponent(comp, user.getEmail()); + + if (CommonUtils.isNotNullEmptyOrWhitespace(compAddSummary.getId())) { + comp.setId(compAddSummary.getId()); + if (AddDocumentRequestStatus.SUCCESS.equals(compAddSummary.getRequestStatus())) { + } else { + } + } 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; + } + + for (org.cyclonedx.model.Component bomComp : entry.getValue()) { + + Set licenses = getLicenseFromBomComponent(bomComp); + rel = createRelease(bomComp, comp, licenses); + if (CommonUtils.isNullEmptyOrWhitespace(rel.getVersion()) ) { + log.error("release version is not present in SBoM for component: " + comp.getName()); + invalidReleases.add(comp.getName()); + continue; + } + relName = SW360Utils.getVersionedName(rel.getName(), rel.getVersion()); + + try { + AddDocumentRequestSummary relAddSummary = componentDatabaseHandler.addRelease(rel, user); + if (CommonUtils.isNotNullEmptyOrWhitespace(relAddSummary.getId())) { + rel.setId(relAddSummary.getId()); + if (AddDocumentRequestStatus.SUCCESS.equals(relAddSummary.getRequestStatus())) { + relCreationCount = releaseRelationMap.containsKey(rel.getId()) ? relCreationCount : relCreationCount + 1; + } else { + relReuseCount = releaseRelationMap.containsKey(rel.getId()) ? relReuseCount : relReuseCount + 1; + } + } 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; + } + } 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()) { + Set categories = new HashSet<>(comp.getCategories()); + categories.add(bomComp.getType().getTypeName()); + categories.remove(DEFAULT_CATEGORY); + comp.setCategories(categories); + } + if (CommonUtils.isNullEmptyOrWhitespace(comp.getDescription())) { + description.append(CommonUtils.nullToEmptyString(bomComp.getDescription()).trim()); + } else { + description.append(" | ").append(CommonUtils.nullToEmptyString(bomComp.getDescription()).trim()); + } + if (comp.isSetMainLicenseIds()) { + comp.getMainLicenseIds().addAll(licenses); + } else { + comp.setMainLicenseIds(licenses); + } + comp.setDescription(description.toString()); + RequestStatus updateStatus = componentDatabaseHandler.updateComponent(comp, user, true); + if (RequestStatus.SUCCESS.equals(updateStatus)) { + log.info("updating component successfull: " + comp.getName()); + } + + Package pkg = createPackage(bomComp, rel, licenses); + String pkgName = SW360Utils.getVersionedName(rel.getName(), rel.getVersion()); + if (pkg == null || CommonUtils.isNullEmptyOrWhitespace(pkg.getName()) || CommonUtils.isNullEmptyOrWhitespace(pkg.getVersion()) + || CommonUtils.isNullEmptyOrWhitespace(pkg.getPurl())) { + invalidPackages.add(pkgName); + log.error(String.format("Invalid package '%s' found in SBoM, missing name or version or purl! ", pkgName)); + continue; + } + + try { + AddDocumentRequestSummary pkgAddSummary = packageDatabaseHandler.addPackage(pkg, user); + if (CommonUtils.isNotNullEmptyOrWhitespace(pkgAddSummary.getId())) { + pkg.setId(pkgAddSummary.getId()); + if (AddDocumentRequestStatus.DUPLICATE.equals(pkgAddSummary.getRequestStatus())) { + Package dupPkg = packageDatabaseHandler.getPackageById(pkg.getId()); + if (!rel.getId().equals(dupPkg.getReleaseId())) { + log.error("Release Id of Package from BOM: '%s' and Database: '%s' is not equal!", rel.getId(), dupPkg.getReleaseId()); + dupPkg.setReleaseId(rel.getId()); + packageDatabaseHandler.updatePackage(dupPkg, user, true); + } + pkgReuseCount++; + } else { + pkgCreationCount++; + } + } else { + // in case of more than 1 duplicate found, then continue and show error message in UI. + log.warn("found multiple packages: " + pkgName); + duplicatePackages.add(pkgName); + continue; + } + project.addToPackageIds(pkg.getId()); + releaseRelationMap.putIfAbsent(rel.getId(), getDefaultRelation()); + } catch (SW360Exception e) { + log.error("An error occured while creating/adding package from SBOM: " + e.getMessage()); + continue; + } + } + } 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(DUPLICATE_PACKAGE, String.join(JOINER, duplicatePackages)); + messageMap.put(INVALID_RELEASE, String.join(JOINER, invalidReleases)); + messageMap.put(INVALID_PACKAGE, String.join(JOINER, invalidPackages)); + messageMap.put(PROJECT_ID, project.getId()); + messageMap.put(PROJECT_NAME, SW360Utils.getVersionedName(project.getName(), project.getVersion())); + messageMap.put(REL_CREATION_COUNT_KEY, String.valueOf(relCreationCount)); + messageMap.put(REL_REUSE_COUNT_KEY, String.valueOf(relReuseCount)); + messageMap.put(PKG_CREATION_COUNT_KEY, String.valueOf(pkgCreationCount)); + messageMap.put(PKG_REUSE_COUNT_KEY, String.valueOf(pkgReuseCount)); + return messageMap; + } + + private Set getLicenseFromBomComponent(org.cyclonedx.model.Component comp) { + Set licenses = new HashSet<>(); + if (Objects.nonNull(comp.getLicenseChoice()) && CommonUtils.isNotEmpty(comp.getLicenseChoice().getLicenses())) { + licenses.addAll(comp.getLicenseChoice().getLicenses().stream() + .map(lic -> (null == lic.getId()) ? lic.getName() : lic.getId()).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(String name) { + Component component = new Component(); + component.setName(name); + component.setComponentType(ComponentType.OSS); + return component; + } + + private Component createComponent(org.cyclonedx.model.Component componentFromBom) { + Component component = new Component(); + component.setName(getComponentName(componentFromBom).trim()); + component.setComponentType(ComponentType.OSS); + return component; + } + + private String getComponentName(org.cyclonedx.model.Component comp) { + String name = CommonUtils.nullToEmptyString(comp.getName()); + if (CommonUtils.isNotNullEmptyOrWhitespace(comp.getGroup())) { + return new StringBuilder(comp.getGroup()).append(DOT).append(name).toString(); + } else if (CommonUtils.isNotNullEmptyOrWhitespace(comp.getPublisher())) { + return new StringBuilder(comp.getPublisher().replaceAll(CLEAN_PUBLISHER_REGEX, StringUtils.EMPTY)).append(DOT).append(name).toString(); + } else if (CommonUtils.isNotNullEmptyOrWhitespace(comp.getAuthor())) { + //.replaceAll(CLEAN_PUBLISHER_REGEX, StringUtils.EMPTY) --> Use this to remove Publisher email id. + return new StringBuilder(comp.getAuthor().replaceAll(CLEAN_PUBLISHER_REGEX, StringUtils.EMPTY)).append(DOT).append(name).toString(); + } else { + return name; + } + } + + 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); + } + return release; + } + + private static ProjectReleaseRelationship getDefaultRelation() { + return new ProjectReleaseRelationship(ReleaseRelationship.UNKNOWN, MainlineState.OPEN); + } + + private String getPackageName(PackageURL packageURL, org.cyclonedx.model.Component comp, String delimiter) { + String name = CommonUtils.nullToEmptyString(packageURL.getName()); + if (CommonUtils.isNotNullEmptyOrWhitespace(packageURL.getNamespace())) { + return new StringBuilder(packageURL.getNamespace()).append(delimiter).append(name).toString(); + } else if (CommonUtils.isNotNullEmptyOrWhitespace(comp.getGroup())) { + return new StringBuilder(comp.getGroup()).append(delimiter).append(name).toString(); + } else if (CommonUtils.isNotNullEmptyOrWhitespace(comp.getPublisher())) { + //.replaceAll(CLEAN_PUBLISHER_REGEX, StringUtils.EMPTY) --> Use this to remove Publisher email id. + return new StringBuilder(comp.getPublisher()).append(delimiter).append(name).toString(); + } else { + return name; + } + } + + private Package createPackage(org.cyclonedx.model.Component componentFromBom, Release release, Set licenses) { + Package pckg = new Package(); + String purl = componentFromBom.getPurl(); + if (CommonUtils.isNotNullEmptyOrWhitespace(purl)) { + try { + purl = purl.toLowerCase().trim(); + PackageURL packageURL = new PackageURL(purl); + pckg.setPurl(purl); + String packageName = StringUtils.EMPTY; + if (PackageManagerType.NPM.toString().equalsIgnoreCase(packageURL.getType()) + || PackageManagerType.GOLANG.toString().equalsIgnoreCase(packageURL.getType())) { + packageName = getPackageName(packageURL, componentFromBom, SLASH).trim(); + } else { + packageName = getPackageName(packageURL, componentFromBom, DOT).replaceAll(SLASH, DOT).trim(); + } + pckg.setName(packageName); + pckg.setVersion(packageURL.getVersion()); + } catch (MalformedPackageURLException e) { + log.error("Malformed PURL for component: " + componentFromBom.getName(), e); + return null; + } + } else { + return null; + } + + if (release != null && release.isSetId()) { + pckg.setReleaseId(release.getId()); + } + pckg.setDescription(CommonUtils.nullToEmptyString(componentFromBom.getDescription()).trim()); + + for (ExternalReference ref : CommonUtils.nullToEmptyList(componentFromBom.getExternalReferences())) { + if (ExternalReference.Type.WEBSITE.equals(ref.getType())) { + pckg.setHomepageUrl(ref.getUrl()); + } + if (ExternalReference.Type.VCS.equals(ref.getType())) { + pckg.setVcs(ref.getUrl().replaceAll(VCS_HTTP_REGEX, HTTPS_SCHEME)); + } + } + + if (pckg.isSetLicenseIds()) { + pckg.getLicenseIds().addAll(licenses); + } else { + pckg.setLicenseIds(licenses); + } + return pckg; + } +} diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentAwareDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentAwareDatabaseHandler.java index 076cd224c3..34c50559a9 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentAwareDatabaseHandler.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/AttachmentAwareDatabaseHandler.java @@ -28,6 +28,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.packages.Package; import org.eclipse.sw360.datahandler.thrift.projects.Project; import com.cloudant.client.api.CloudantClient; @@ -101,6 +102,10 @@ protected void deleteAttachmentUsagesOfUnlinkedReleases(Source usedBy, Set httpClient, String dbName, String attachmentDbName, ComponentModerator moderator, ReleaseModerator releaseModerator, ProjectModerator projectModerator) throws MalformedURLException { super(httpClient, dbName, attachmentDbName); DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(httpClient, dbName); @@ -160,6 +165,7 @@ public ComponentDatabaseHandler(Supplier httpClient, String dbNa componentRepository = new ComponentRepository(db, releaseRepository, vendorRepository); projectRepository = new ProjectRepository(db); userRepository = new UserRepository(db); + packageRepository = new PackageRepository(db); // Create the moderator this.moderator = moderator; @@ -185,6 +191,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 +495,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) @@ -497,7 +504,8 @@ public AddDocumentRequestSummary addRelease(Release release, User user) throws S return addDocumentRequestSummary; } - if (!isDependenciesExistsInRelease(release)) { + if (!isDependenciesExistsInRelease(release) + || verifyLinkedPackages(Collections.emptySet(), CommonUtils.nullToEmptySet(release.getPackageIds()), "", user)) { return new AddDocumentRequestSummary() .setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT); } @@ -542,7 +550,8 @@ public AddDocumentRequestSummary addRelease(Release release, User user) throws S updateReleaseDependentFieldsForComponent(component, release); updateModifiedFields(component, user.getEmail()); componentRepository.update(component); - + // update linked packages + updateLinkedPackages(Collections.emptySet(), CommonUtils.nullToEmptySet(release.getPackageIds()), id, user); sendMailNotificationsForNewRelease(release, user.getEmail()); dbHandlerUtil.addChangeLogs(release, null, user.getEmail(), Operation.CREATE, attachmentConnector, Lists.newArrayList(), null, null); @@ -573,7 +582,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; } @@ -734,6 +743,11 @@ private boolean isDependenciesExistsInRelease(Release release) { String vendorId = release.getVendorId(); isValidDependentIds = DatabaseHandlerUtil.isAllIdInSetExists(Sets.newHashSet(vendorId), vendorRepository); } + + if (isValidDependentIds && release.isSetPackageIds()) { + Set pacakgeIds = release.getPackageIds(); + isValidDependentIds = DatabaseHandlerUtil.isAllIdInSetExists(pacakgeIds, packageRepository); + } return isValidDependentIds; } @@ -1061,7 +1075,8 @@ public RequestStatus updateRelease(Release release, User user, Iterable permissions = makePermission(actual, user); @@ -1107,6 +1122,8 @@ public RequestStatus updateRelease(Release release, User user, Iterable 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); @@ -1342,6 +1359,74 @@ public Component updateReleaseDependentFieldsForComponentId(String componentId, return component; } + /** + * return false if verification is successful + * return true if verification is failed + * verify existence of newly linked packageIds + * verify all newly linked packages are orphan packages + **/ + private boolean verifyLinkedPackages(Set currentPackageIds, Set updatedPackageIds, String releaseId, User user) throws SW360Exception { + Set addedPacakgeIds = Sets.difference(updatedPackageIds, currentPackageIds); + PackageService.Iface packageClient = new ThriftClients().makePackageClient(); + if (CommonUtils.isNotEmpty(addedPacakgeIds)) { + try { + long addedCount = addedPacakgeIds.size(); + List addedPackages = packageClient.getPackageByIds(addedPacakgeIds); + Predicate orphanReleaseFilter = pkg -> CommonUtils.isNullEmptyOrWhitespace(pkg.getReleaseId()); + Predicate linkedReleaseFilter = pkg -> releaseId.equals(pkg.getReleaseId()); + long orphanCount = addedPackages.stream().filter(orphanReleaseFilter).count(); + long linkedCount = addedPackages.stream().filter(linkedReleaseFilter).count(); + if (CommonUtils.isNotNullEmptyOrWhitespace(releaseId) && addedCount != orphanCount) { + return addedCount != linkedCount; + } else { + return addedCount != orphanCount; + } + } catch (TException e) { + log.error(String.format("An error occured while updating linked packages of release: %s", releaseId), e.getCause()); + return true; + } + } + return false; + } + + private void updateLinkedPackages(Set currentPackageIds, Set updatedPackageIds, String releaseId, User user) throws SW360Exception { + Set removedPacakgeIds = Sets.difference(currentPackageIds, updatedPackageIds); + Set addedPacakgeIds = Sets.difference(updatedPackageIds, currentPackageIds); + PackageService.Iface packageClient = new ThriftClients().makePackageClient(); + try { + if (CommonUtils.isNotEmpty(removedPacakgeIds)) { + List removedPackages = packageRepository.get(removedPacakgeIds); + for (Package pkg : removedPackages) { + String relId = pkg.getReleaseId(); + // update the package, if it contains linked release Id + if (CommonUtils.isNotNullEmptyOrWhitespace(relId) && releaseId.equals(relId)) { + pkg.unsetReleaseId(); + RequestStatus status = packageClient.updatePackage(pkg, user); + log.info(String.format("Unlinked package <%s> from release <%s>, Unlinking status: <%s>", pkg.getId(), releaseId, status.name())); + } + } + } + if (CommonUtils.isNotEmpty(addedPacakgeIds)) { + List addedPackages = packageClient.getPackageByIds(addedPacakgeIds); + for (Package pkg : addedPackages) { + String relId = pkg.getReleaseId(); + // update only orphan packages + if (CommonUtils.isNullEmptyOrWhitespace(relId)) { + pkg.setReleaseId(releaseId); + RequestStatus status = packageClient.updatePackage(pkg, user); + log.info(String.format("Linked package <%s> to release <%s>, Linking status: <%s>", pkg.getId(), releaseId, status.name())); + } else if (!relId.equals(releaseId)) { + log.warn(String.format("Linked-ReleasId <%s> in Package <%s>, and Linked-PackageId <%s> in Release <%s> association is incorrect", + relId, pkg.getId(), pkg.getId(), releaseId)); + } + } + } + } catch (TException e) { + log.error(String.format("An error occured while updating linked packages of release: %s", releaseId), e.getCause()); + throw new SW360Exception(e.getMessage()); + } + } + public void recomputeReleaseDependentFields(Component component, String skipThisReleaseId) { resetReleaseDependentFields(component); @@ -1771,7 +1856,7 @@ public RequestStatus deleteRelease(String id, User user, boolean forceDelete) th Release release = releaseRepository.get(id); assertNotNull(release); - if (checkIfInUse(id)) return RequestStatus.IN_USE; + if (release.getPackageIdsSize() > 0 || checkIfInUse(id)) return RequestStatus.IN_USE; if (makePermission(release, user).isActionAllowed(RequestedAction.DELETE) || forceDelete) { Component componentBefore = componentRepository.get(release.getComponentId()); @@ -1968,6 +2053,11 @@ public List getReleases(Set ids) { return releaseRepository.makeSummary(SummaryType.SHORT, ids); } + // return direct release from db, without making summary. + public List getReleasesWithoutSummary(Set ids) { + return releaseRepository.get(ids); + } + public List getAccessibleReleases(Set ids, User user) { return getAccessibleReleaseList(releaseRepository.makeSummary(SummaryType.SHORT, ids), user); } @@ -2192,7 +2282,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..6e06d91ed8 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 @@ -96,6 +96,7 @@ import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.components.Repository; import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest; +import org.eclipse.sw360.datahandler.thrift.packages.Package; import org.eclipse.sw360.datahandler.thrift.projects.ObligationStatusInfo; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectProjectRelationship; @@ -379,6 +380,13 @@ public static void trimStringFields(T obj, List listOfStrFields) { if (fieldValueObj instanceof String) { eccInformation.setFieldValue(eccInformationField, fieldValueObj.toString().trim()); } + } else if (obj instanceof Package) { + Package._Fields pkgField = (Package._Fields) strField; + Package pkg = (Package) obj; + Object fieldValueObj = pkg.getFieldValue(pkgField); + if (fieldValueObj instanceof String) { + pkg.setFieldValue(pkgField, fieldValueObj.toString().trim()); + } } }); } @@ -400,9 +408,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 +420,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 +450,14 @@ 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); + } else if (newDocVersion instanceof Package) { + Package newPkgVer = (Package) newDocVersion; + changeLog.setDocumentId(newPkgVer.getId()); + changeLog.setDocumentType(newPkgVer.getType()); changeLog.setDbName(DatabaseSettings.COUCH_DB_DATABASE); } @@ -576,7 +589,6 @@ private static Runnable prepareChangeLogRunnable(T newDocVersi List referenceDocLogList, String parentDocId, Operation parentOperation) { return () -> { try { - log.info("Generating ChangeLogs."); ChangeLogs changeLogParent = initChangeLogsObj(newDocVersion, userEdited, parentDocId, operation, parentOperation); if (oldDocVersion == null) { @@ -635,6 +647,8 @@ private static void changeLogsForNewlyCreatedOrDeleted(T newor fields = PackageInformation._Fields.values(); } else if (neworDeletedVersion instanceof Obligation) { fields = Obligation._Fields.values(); + } else if (neworDeletedVersion instanceof Package) { + fields = Package._Fields.values(); } else { return; } @@ -667,6 +681,8 @@ private static void changeLogsForNewlyCreatedOrDeleted(T newor fields = PackageInformation._Fields.values(); } else if (newVersion instanceof Obligation) { fields = Obligation._Fields.values(); + } else if (newVersion instanceof Package) { + fields = Package._Fields.values(); } else { return; } diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageDatabaseHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageDatabaseHandler.java new file mode 100644 index 0000000000..ebd5be3a8b --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageDatabaseHandler.java @@ -0,0 +1,420 @@ +/* + * 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.datahandler.db; + +import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull; +import static org.eclipse.sw360.datahandler.common.SW360Assert.fail; + +import java.net.MalformedURLException; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.http.HttpStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.sw360.cyclonedx.CycloneDxBOMImporter; +import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.Duration; +import org.eclipse.sw360.datahandler.common.SW360Assert; +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.couchdb.AttachmentConnector; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +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.ThriftUtils; +import org.eclipse.sw360.datahandler.thrift.ThriftValidate; +import org.eclipse.sw360.datahandler.thrift.changelogs.Operation; +import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageManagerType; +import org.eclipse.sw360.datahandler.thrift.users.User; + +import com.cloudant.client.api.CloudantClient; +import com.cloudant.client.api.model.Response; +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * Class for accessing the CouchDB for Packages. + * + * @author: abdul.kapti@siemens-healthineers.com + */ +public class PackageDatabaseHandler extends AttachmentAwareDatabaseHandler { + + private final AttachmentConnector attachmentConnector; + private final DatabaseConnectorCloudant db; + private final PackageRepository packageRepository; + private final ProjectRepository projectRepository; + private final ComponentDatabaseHandler componentDatabaseHandler; + private final DatabaseHandlerUtil databaseHandlerUtil; + + private static final Logger log = LogManager.getLogger(CycloneDxBOMImporter.class); + + private static final ImmutableList listOfStringFieldsInPackageToTrim = ImmutableList.of( + Package._Fields.NAME, Package._Fields.VERSION, Package._Fields.VCS, Package._Fields.DESCRIPTION, + Package._Fields.HOMEPAGE_URL, Package._Fields.PURL, Package._Fields.HASH); + + public PackageDatabaseHandler(Supplier httpClient, String dbName, String attachmentDbName, String changeLogsDbName, + AttachmentDatabaseHandler attachmentDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler) throws MalformedURLException { + + super(attachmentDatabaseHandler); + db = new DatabaseConnectorCloudant(httpClient, dbName); + + // Create the repositories + packageRepository = new PackageRepository(db); + projectRepository = new ProjectRepository(db); + + // Create the attachment connector + attachmentConnector = new AttachmentConnector(httpClient, attachmentDbName, Duration.durationOf(30, TimeUnit.SECONDS)); + + this.componentDatabaseHandler = componentDatabaseHandler; + DatabaseConnectorCloudant changeLogsDb = new DatabaseConnectorCloudant(httpClient, changeLogsDbName); + this.databaseHandlerUtil = new DatabaseHandlerUtil(changeLogsDb); + } + + public PackageDatabaseHandler(Supplier httpClient, String dbName, String changeLogsDbName, String attachmentDbName) + throws MalformedURLException { + + this(httpClient, dbName, attachmentDbName, changeLogsDbName, new AttachmentDatabaseHandler(httpClient, dbName, attachmentDbName), + new ComponentDatabaseHandler(httpClient, dbName, changeLogsDbName, attachmentDbName)); + } + + public Package getPackageById(String id) throws SW360Exception { + Package pkg = packageRepository.get(id); + assertNotNull(pkg, "Invalid Package Id"); + return pkg; + } + + public List getPackageByIds(Set ids) throws SW360Exception { + List packages = packageRepository.get(ids); + SW360Assert.assertEquals(ids.size(), CommonUtils.nullToEmptyList(packages).size(), "At least one package id was invalid!" ); + return packages; + } + + public List getPackageWithReleaseByPackageIds(Set ids) throws SW360Exception { + List packages = packageRepository.get(ids); + Set releaseIds = packages.stream().filter(pkg -> CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) + .map(Package::getReleaseId).collect(Collectors.toSet()); + if (CommonUtils.isNotEmpty(releaseIds)) { + List releases = componentDatabaseHandler.getReleasesWithoutSummary(releaseIds); + Map releaseIdToReleaseMap = releases.stream() + .map(rel -> new AbstractMap.SimpleEntry<>(rel.getId(), rel)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal)); + packages.forEach(pkg -> { + if (CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) { + pkg.setRelease(releaseIdToReleaseMap.get(pkg.getReleaseId())); + } + }); + } + return packages; + } + + public Set getPackagesByReleaseId(String id) { + Set packages = Sets.newHashSet(); + packages.addAll(packageRepository.getPackagesByReleaseId(id)); + return packages; + } + + public Set getPackagesByReleaseIds(Set ids) { + Set packages = Sets.newHashSet(); + for (String id : ids) { + packages.addAll(packageRepository.getPackagesByReleaseId(id)); + } + return packages; + } + + public List getAllPackages() { + return packageRepository.getAll(); + } + + public List getAllOrphanPackages() { + return packageRepository.getOrphanPackage(); + } + + public List searchPackages(PackageSearchHandler searchHandler, String searchText) { + return searchHandler.searchPackages(searchText); + } + + public List searchPackagesWithFilter(String searchText, PackageSearchHandler searchHandler, Map> subQueryRestrictions) { + return searchHandler.searchPackagesWithRestrictions(searchText, subQueryRestrictions); + } + + public int getTotalPackageCount() { + return packageRepository.getDocumentCount(); + } + + public List searchOrphanPackages(PackageSearchHandler searchHandler, String searchText) { + List packages = searchPackages(searchHandler, searchText); + Predicate orphanReleaseFilter = pkg -> CommonUtils.isNullEmptyOrWhitespace(pkg.getReleaseId()); + return packages.stream().filter(orphanReleaseFilter).collect(Collectors.toList()); + } + + private void preparePackage(Package pkg) throws SW360Exception { + // Prepare package for database + ThriftValidate.preparePackage(pkg); + } + + public AddDocumentRequestSummary addPackage(Package pkg, User user) throws SW360Exception { + removeLeadingTrailingWhitespace(pkg); + String name = pkg.getName(); + String version = pkg.getVersion(); + + if (name == null || name.isEmpty() || version == null || version.isEmpty()) { + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.NAMINGERROR); + } + + try { + PackageURL purl = new PackageURL(pkg.getPurl()); + pkg.setPackageManagerType(PackageManagerType.valueOf(purl.getType().toUpperCase())); + } catch (MalformedPackageURLException e) { + log.error(String.format("Invalid PURL for package: '%s' with Id: '%s'", SW360Utils.printName(pkg), pkg.getId()), e); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT).setMessage("Invalid Pacakge URL"); + } catch (IllegalArgumentException e) { + log.error(String.format("Invalid Package Manager type for package: '%s' with Id: '%s'", SW360Utils.printName(pkg), pkg.getId()), e); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT).setMessage("Invalid Pacakge Manager type"); + } + + // Prepare package for database + preparePackage(pkg); + List duplicatePackages = getPackageByNameAndVersion(name, version); + if (duplicatePackages.size() > 0) { + final AddDocumentRequestSummary addDocumentRequestSummary = new AddDocumentRequestSummary() + .setRequestStatus(AddDocumentRequestStatus.DUPLICATE); + if (duplicatePackages.size() == 1) { + addDocumentRequestSummary.setId(duplicatePackages.get(0).getId()); + } + return addDocumentRequestSummary; + } + pkg.setCreatedBy(user.getEmail()); + pkg.setCreatedOn(SW360Utils.getCreatedOn()); + AddDocumentRequestSummary summary = new AddDocumentRequestSummary() + .setRequestStatus(AddDocumentRequestStatus.FAILURE); + + // Ensure that release exists + if (CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) { + try { + Release release = componentDatabaseHandler.getRelease(pkg.getReleaseId(), user); + // create new package + boolean updateStatus = packageRepository.add(pkg); + // Update the underlying release + if (updateStatus) { + release.addToPackageIds(pkg.getId()); + componentDatabaseHandler.updateRelease(release, user, ThriftUtils.IMMUTABLE_OF_RELEASE, true); + summary.setMessage("Package successfully created with linked release"); + } else { + log.error(String.format("Failed to add package: %s ", SW360Utils.printName(pkg))); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.FAILURE).setMessage("Failed to add package!"); + } + } catch (SW360Exception e) { + log.error(String.format("Invalid release id %s while adding package %s ", pkg.getReleaseId(), SW360Utils.printName(pkg)), e); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT).setMessage("Invalid Release Id"); + } + } else { + // create new orphan package (without linked releases) + packageRepository.add(pkg); + summary.setMessage(String.format("An Orphan Package with id: <%s> successfully created (without linked release)", pkg.getId())); + } + databaseHandlerUtil.addChangeLogs(pkg, null, user.getEmail(), Operation.CREATE, attachmentConnector, Lists.newArrayList(), null, null); + return summary.setId(pkg.getId()).setRequestStatus(AddDocumentRequestStatus.SUCCESS); + } + + public RequestStatus updatePackage(Package pkg, User user) throws SW360Exception { + return updatePackage(pkg, user, false); + } + + public RequestStatus updatePackage(Package updatedPkg, User user, boolean forceUpdate) throws SW360Exception { + removeLeadingTrailingWhitespace(updatedPkg); + String name = updatedPkg.getName(); + String version = updatedPkg.getVersion(); + if (CommonUtils.isNullEmptyOrWhitespace(name) || CommonUtils.isNullEmptyOrWhitespace(version)) { + return RequestStatus.NAMINGERROR; + } + + if (!SW360Utils.isWriteAccessUser(updatedPkg, user)) { + log.error(String.format("User %s does not have write access to package: %s ", user.getEmail(), updatedPkg.getId())); + return RequestStatus.ACCESS_DENIED; + } + + if (CommonUtils.isNotNullEmptyOrWhitespace(updatedPkg.getReleaseId()) && + DatabaseHandlerUtil.isAllIdInSetExists(Sets.newHashSet(updatedPkg.getReleaseId()), packageRepository)) { + log.error(String.format("Invalid linked release id %s for package: %s ", updatedPkg.getReleaseId(), updatedPkg.getId())); + return RequestStatus.INVALID_INPUT; + } + + try { + PackageURL purl = new PackageURL(updatedPkg.getPurl()); + updatedPkg.setPackageManagerType(PackageManagerType.valueOf(purl.getType().toUpperCase())); + } catch (MalformedPackageURLException e) { + log.error(String.format("Invalid PURL for package: %s - Error Message: %s", SW360Utils.printName(updatedPkg)), e); + return RequestStatus.INVALID_INPUT; + } catch (IllegalArgumentException e) { + log.error(String.format("Invalid Package Manager type for package: %s - Error Message: %s", SW360Utils.printName(updatedPkg)), e); + return RequestStatus.INVALID_INPUT; + } + + // Prepare package for database + preparePackage(updatedPkg); + // Get actual document for members that should not change + String packageId = updatedPkg.getId(); + Package actualPkg = packageRepository.get(packageId); + if (actualPkg == null) { + throw fail(404, String.format("Could not fetch package from database! id = %s", packageId)); + } + + if (changeWouldResultInDuplicate(actualPkg, updatedPkg)) { + return RequestStatus.DUPLICATE; + } + + copyImmutableFields(updatedPkg, actualPkg); + updateModifiedFields(updatedPkg, user.getEmail()); + + String actualReleaseId = CommonUtils.nullToEmptyString(actualPkg.getReleaseId()); + String newReleaseId = CommonUtils.nullToEmptyString(updatedPkg.getReleaseId()); + // update linked release if there is a change in linked release. + if (!newReleaseId.equals(actualReleaseId)) { + // Ensure that release exists + try { + // once we are sure that release exists, update the package + Response resp = packageRepository.updatewithResponse(updatedPkg); + if (CommonUtils.isNotNullEmptyOrWhitespace(actualReleaseId)) { + Release actualRelease = componentDatabaseHandler.getRelease(actualReleaseId, user); + Set packageIds = CommonUtils.nullToEmptySet(actualRelease.getPackageIds()); + // Update the previously linked release, if it contain package id + if (resp.getStatusCode() == HttpStatus.SC_CREATED) { + if (packageIds.contains(packageId)) { + packageIds.remove(packageId); + actualRelease.setPackageIds(packageIds); + componentDatabaseHandler.updateRelease(actualRelease, user, ThriftUtils.IMMUTABLE_OF_RELEASE, true); + } else { + log.info(String.format("Linked pacakgeId: %s is not present in release: %s", packageId, actualReleaseId)); + } + } else { + log.error(String.format("Failed to update package: %s with id: %s", SW360Utils.printName(updatedPkg), packageId)); + return RequestStatus.FAILURE; + } + } + if (CommonUtils.isNotNullEmptyOrWhitespace(newReleaseId)) { + Release newRelease = componentDatabaseHandler.getRelease(newReleaseId, user); + Set packageIds = CommonUtils.nullToEmptySet(newRelease.getPackageIds()); + // Update the newly linked release, if it does not contain package id + if (resp.getStatusCode() == HttpStatus.SC_CREATED) { + if (!packageIds.contains(packageId)) { + newRelease.addToPackageIds(packageId); + componentDatabaseHandler.updateRelease(newRelease, user, ThriftUtils.IMMUTABLE_OF_RELEASE, true); + } else { + log.info(String.format("Linked pacakgeId: %s is already present in release: %s", packageId, newReleaseId)); + } + } else { + log.error(String.format("Failed to update package: %s with id: %s", SW360Utils.printName(updatedPkg), packageId)); + return RequestStatus.FAILURE; + } + } + + } catch (SW360Exception e) { + log.error(String.format("Invalid release id %s while adding package %s ", newReleaseId, SW360Utils.printName(updatedPkg)), e); + return RequestStatus.INVALID_INPUT; + } + } else { + // update orphan package (without linked releases) + packageRepository.update(updatedPkg); + } + databaseHandlerUtil.addChangeLogs(updatedPkg, actualPkg, user.getEmail(), Operation.UPDATE, attachmentConnector, Lists.newArrayList(), null, null); + return RequestStatus.SUCCESS; + } + + public RequestStatus deletePackage(String id, User user) throws SW360Exception { + return deletePackage(id, user, false); + } + + public RequestStatus deletePackage(String id, User user, boolean forceDelete) throws SW360Exception { + Package pkg = packageRepository.get(id); + assertNotNull(pkg); + + if (checkIfInUse(id)) { + return RequestStatus.IN_USE; + } + + if (!SW360Utils.isWriteAccessUser(pkg, user)) { + log.error(String.format("User %s does not have write access to package: %s ", user.getEmail(), pkg.getId())); + return RequestStatus.ACCESS_DENIED; + } + + packageRepository.remove(pkg); + RequestStatus status = cleanupPackageDependentFieldsInRelease(pkg, user); + if (RequestStatus.SUCCESS.equals(status)) { + databaseHandlerUtil.addChangeLogs(null, pkg, user.getEmail(), Operation.DELETE, attachmentConnector, + Lists.newArrayList(), null, null); + return status; + } + return status; + } + + private boolean checkIfInUse(String packageId) { + return projectRepository.getCountByPackageId(packageId) > 0; + } + + private RequestStatus cleanupPackageDependentFieldsInRelease(Package pkg, User user) throws SW360Exception { + String releaseId = pkg.getReleaseId(); + if (CommonUtils.isNotNullEmptyOrWhitespace(releaseId)) { + Release release = componentDatabaseHandler.getRelease(releaseId, user); + Set packageIds = release.getPackageIds(); + if (CommonUtils.isNotEmpty(packageIds) && packageIds.contains(pkg.getId())) { + packageIds.remove(pkg.getId()); + release.setPackageIds(packageIds); + return componentDatabaseHandler.updateRelease(release, user, ThriftUtils.IMMUTABLE_OF_RELEASE, true); + } + } + return RequestStatus.SUCCESS; + } + + private boolean changeWouldResultInDuplicate(Package before, Package after) { + if (before.getName().equalsIgnoreCase(after.getName()) + && before.getVersion().equalsIgnoreCase(after.getVersion())) { + return false; + } + List duplicates = getPackageByNameAndVersion(after.getName(), after.getVersion()); + return duplicates.size() > 0; + } + + private List getPackageByNameAndVersion(String pkgName, String pkgVersion) { + if (isNullEmptyOrWhitespace(pkgName)) { + return Collections.emptyList(); + } + return packageRepository.searchByNameAndVersion(pkgName, pkgVersion, true); + } + + private void copyImmutableFields(Package destination, Package source) { + ThriftUtils.copyField(source, destination, Package._Fields.CREATED_ON); + ThriftUtils.copyField(source, destination, Package._Fields.CREATED_BY); + } + + private void removeLeadingTrailingWhitespace(Package pkg) { + DatabaseHandlerUtil.trimStringFields(pkg, listOfStringFieldsInPackageToTrim); + pkg.setLicenseIds(DatabaseHandlerUtil.trimSetOfString(pkg.getLicenseIds())); + } + + public Map> getPackagesWithPagination(PaginationData pageData) { + return packageRepository.getPackagesWithPagination(pageData); + } +} diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageRepository.java new file mode 100644 index 0000000000..33ff81fb31 --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageRepository.java @@ -0,0 +1,165 @@ +/* + * 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.datahandler.db; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; +import org.eclipse.sw360.datahandler.cloudantclient.DatabaseRepositoryCloudantClient; +import org.eclipse.sw360.datahandler.thrift.PaginationData; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.users.User; + +import com.cloudant.client.api.model.DesignDocument.MapReduce; +import com.cloudant.client.api.views.Key; +import com.cloudant.client.api.views.ViewRequest; +import com.cloudant.client.api.views.ViewRequestBuilder; +import com.cloudant.client.api.views.ViewResponse; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * CRUD access for the Package class + * + * @author abdul.kapti@siemens-healthineers.com + */ + +public class PackageRepository extends DatabaseRepositoryCloudantClient { + + private static final String ALL = "function(doc) { if (doc.type == 'package') emit(null, doc._id) }"; + private static final String ORPHAN = "function(doc) { if (doc.type == 'package' && !doc.releaseId) emit(null, doc._id) }"; + private static final String BY_NAME = "function(doc) { if (doc.type == 'package') { emit(doc.name.trim(), doc._id) } }"; + private static final String BY_NAME_LOWERCASE = "function(doc) { if (doc.type == 'package') { emit(doc.name.toLowerCase().trim(), doc._id) } }"; + private static final String BY_VESION_LOWERCASE = "function(doc) { if (doc.type == 'package') { emit(doc.version.toLowerCase().trim(), doc._id) } }"; + private static final String BY_PKG_MANAGER_TYPE = "function(doc) { if (doc.type == 'package') { emit(doc.packageManagerType.toLowerCase().trim(), doc._id) } }"; + private static final String BY_CREATOR = "function(doc) { if (doc.type == 'package') { emit(doc.createdBy, doc._id) } }"; + private static final String BY_CREATED_ON = "function(doc) { if (doc.type == 'package') { emit(doc.createdOn, doc._id) } }"; + private static final String BY_RELEASE_ID = "function(doc) { if (doc.type == 'package') { emit(doc.releaseId, doc._id); } }"; + private static final String BY_LICENSE_IDS = "function(doc) { if (doc.type == 'package') { if (doc.licenseIds) { emit(doc.licenseIds.join(), doc._id); } else { emit('', doc._id); } } }"; + + public PackageRepository(DatabaseConnectorCloudant db) { + super(db, Package.class); + Map views = new HashMap(); + views.put("all", createMapReduce(ALL, null)); + views.put("orphan", createMapReduce(ORPHAN, null)); + views.put("byName", createMapReduce(BY_NAME, null)); + views.put("byNameLowerCase", createMapReduce(BY_NAME_LOWERCASE, null)); + views.put("byVersionLowerCase", createMapReduce(BY_VESION_LOWERCASE, null)); + views.put("byPackageManagerType", createMapReduce(BY_PKG_MANAGER_TYPE, null)); + views.put("byCreator", createMapReduce(BY_CREATOR, null)); + views.put("byCreatedOn", createMapReduce(BY_CREATED_ON, null)); + views.put("byReleaseId", createMapReduce(BY_RELEASE_ID, null)); + views.put("byLicenseIds", createMapReduce(BY_LICENSE_IDS, null)); + initStandardDesignDocument(views, db); + } + + public List getOrphanPackage() { + return queryView("orphan"); + } + + public List getPackagesByReleaseId(String id) { + return queryView("byReleaseId", id); + } + + public List searchByName(String name, User user) { + return queryView("byName", name); + } + + public List searchByNameLowerCase(String name, User user) { + return queryView("byNameLowerCase", name.toLowerCase()); + } + + public List searchByVersionLowerCase(String version, User user) { + return queryView("byVersionLowerCase", version.toLowerCase()); + } + + public List searchByPackageManagerType(String pkgType, User user) { + return queryView("byPackageManagerType", pkgType.toUpperCase()); + } + + public List searchByCreator(String email, User user) { + return queryView("byCreator", email); + } + + public List searchByLicenseeId(String id) { + return queryView("byLicenseIds", id); + } + + public List searchByNameAndVersion(String name, String version, boolean caseInsenstive) { + List packagesMatchingName; + if (caseInsenstive) { + packagesMatchingName = new ArrayList(queryView("byNameLowerCase", name.toLowerCase())); + } else { + packagesMatchingName = new ArrayList(queryView("byName", name)); + } + List releasesMatchingNameAndVersion = packagesMatchingName.stream() + .filter(r -> isNullOrEmpty(version) ? isNullOrEmpty(r.getVersion()) : version.equalsIgnoreCase(r.getVersion())) + .collect(Collectors.toList()); + return releasesMatchingNameAndVersion; + } + + public Map> getPackagesWithPagination(PaginationData pageData) { + final int rowsPerPage = pageData.getRowsPerPage(); + Map> result = Maps.newHashMap(); + List packages = Lists.newArrayList(); + final boolean ascending = pageData.isAscending(); + final int sortColumnNo = pageData.getSortColumnNumber(); + + ViewRequestBuilder query; + switch (sortColumnNo) { + case -1: + query = getConnector().createQuery(Package.class, "byCreatedOn"); + break; + case 0: + query = getConnector().createQuery(Package.class, "byNameLowerCase"); + break; + case 3: + query = getConnector().createQuery(Package.class, "byLicenseIds"); + break; + case 4: + query = getConnector().createQuery(Package.class, "byPackageManagerType"); + break; + default: + query = getConnector().createQuery(Package.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++; + } + packages = response.getDocsAs(Package.class); + } catch (Exception e) { + log.error("Error getting packages from repository: ", e); + } + pageData.setTotalRowCount(response.getTotalRowCount()); + result.put(pageData, packages); + return result; + } +} diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageSearchHandler.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageSearchHandler.java new file mode 100644 index 0000000000..1f8e9d7bbf --- /dev/null +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/PackageSearchHandler.java @@ -0,0 +1,95 @@ +/* + * 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.datahandler.db; + +import org.eclipse.sw360.datahandler.couchdb.lucene.LuceneAwareDatabaseConnector; +import org.eclipse.sw360.datahandler.couchdb.lucene.LuceneSearchView; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.ektorp.http.HttpClient; + +import com.cloudant.client.api.CloudantClient; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import static org.eclipse.sw360.datahandler.couchdb.lucene.LuceneAwareDatabaseConnector.prepareWildcardQuery; + +public class PackageSearchHandler { + + private static final LuceneSearchView luceneSearchView = new LuceneSearchView("lucene", "packages", + "function(doc) {" + + " var ret = new Document();" + + " if (!doc.type) return ret;" + + " if (doc.type != 'package') return ret;" + + " function idx(obj) {" + + " for (var key in obj) {" + + " switch (typeof obj[key]) {" + + " case 'object':" + + " idx(obj[key]);" + + " break;" + + " case 'function':" + + " break;" + + " default:" + + " ret.add(obj[key]);" + + " break;" + + " }" + + " }" + + " };" + + " idx(doc);" + + " if (doc.name) { "+ + " ret.add(doc.name, {\"field\": \"name\"} );" + + " }" + + " if (doc.version) { "+ + " ret.add(doc.version, {\"field\": \"version\"} );" + + " }" + + " if (doc.purl) { "+ + " ret.add(doc.purl, {\"field\": \"purl\"} );" + + " }" + + " if (doc.releaseId) { "+ + " ret.add(doc.releaseId, {\"field\": \"releaseId\"} );" + + " }" + + " if (doc.vcs) { "+ + " ret.add(doc.vcs, {\"field\": \"vcs\"} );" + + " }" + + " if (doc.packageManagerType) { "+ + " ret.add(doc.packageManagerType, {\"field\": \"packageManagerType\"} );" + + " }" + + " if (doc.createdBy) { "+ + " ret.add(doc.createdBy, {\"field\": \"createdBy\"} );" + + " }" + + " if (doc.createdOn) { "+ + " ret.add(doc.createdOn, {\"field\": \"createdOn\", \"type\": \"date\"} );" + + " }" + + " for (var i in doc.licenseIds) {" + + " ret.add(doc.licenseIds[i], {\"field\": \"licenseIds\"} );" + + " }" + + " return ret;" + + "}"); + + + private final LuceneAwareDatabaseConnector connector; + + public PackageSearchHandler(Supplier httpClient, Supplier cloudantClient, String dbName) throws IOException { + connector = new LuceneAwareDatabaseConnector(httpClient, cloudantClient, dbName); + connector.addView(luceneSearchView); + } + + public List searchPackagesWithRestrictions(String text, final Map> subQueryRestrictions) { + return connector.searchViewWithRestrictions(Package.class, luceneSearchView, text, subQueryRestrictions); + } + + public List searchPackages(String searchText) { + return connector.searchView(Package.class, luceneSearchView, prepareWildcardQuery(searchText)); + } + +} 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 232aa8ee53..5fa83ccb8e 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.CycloneDxBOMImporter; import org.eclipse.sw360.datahandler.businessrules.ReleaseClearingStateSummaryComputer; import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; import org.eclipse.sw360.datahandler.common.*; @@ -37,6 +40,8 @@ import org.eclipse.sw360.datahandler.thrift.changelogs.Operation; import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.moderation.ModerationRequest; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; import org.eclipse.sw360.datahandler.thrift.projects.*; import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -54,6 +59,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; @@ -96,6 +102,8 @@ public class ProjectDatabaseHandler extends AttachmentAwareDatabaseHandler { private final ProjectModerator moderator; private final AttachmentConnector attachmentConnector; private final ComponentDatabaseHandler componentDatabaseHandler; + private final PackageDatabaseHandler packageDatabaseHandler; + private final PackageRepository packageRepository; private static final Pattern PLAUSIBLE_GID_REGEXP = Pattern.compile("^[zZ].{7}$"); private final RelationsUsageRepository relUsageRepository; @@ -134,27 +142,29 @@ public class ProjectDatabaseHandler extends AttachmentAwareDatabaseHandler { public ProjectDatabaseHandler(Supplier httpClient, String dbName, String attachmentDbName) throws MalformedURLException { this(httpClient, dbName, attachmentDbName, new ProjectModerator(), new ComponentDatabaseHandler(httpClient,dbName,attachmentDbName), + new PackageDatabaseHandler(httpClient, dbName, DatabaseSettings.COUCH_DB_CHANGE_LOGS, attachmentDbName), new AttachmentDatabaseHandler(httpClient, dbName, attachmentDbName)); } public ProjectDatabaseHandler(Supplier httpClient, String dbName, String changeLogDbName, String attachmentDbName) throws MalformedURLException { this(httpClient, dbName, changeLogDbName, attachmentDbName, new ProjectModerator(), - new ComponentDatabaseHandler(httpClient,dbName,attachmentDbName), + new ComponentDatabaseHandler(httpClient, dbName, attachmentDbName), + new PackageDatabaseHandler(httpClient, dbName, changeLogDbName, attachmentDbName), new AttachmentDatabaseHandler(httpClient, dbName, attachmentDbName)); } @VisibleForTesting public ProjectDatabaseHandler(Supplier httpClient, String dbName, String changeLogsDbName, String attachmentDbName, ProjectModerator moderator, - ComponentDatabaseHandler componentDatabaseHandler, + ComponentDatabaseHandler componentDatabaseHandler, PackageDatabaseHandler packageDatabaseHandler, AttachmentDatabaseHandler attachmentDatabaseHandler) throws MalformedURLException { - this(httpClient, dbName, attachmentDbName, moderator, componentDatabaseHandler, attachmentDatabaseHandler); + this(httpClient, dbName, attachmentDbName, moderator, componentDatabaseHandler, packageDatabaseHandler, attachmentDatabaseHandler); DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(httpClient, changeLogsDbName); this.dbHandlerUtil = new DatabaseHandlerUtil(db); } @VisibleForTesting public ProjectDatabaseHandler(Supplier httpClient, String dbName, String attachmentDbName, ProjectModerator moderator, - ComponentDatabaseHandler componentDatabaseHandler, + ComponentDatabaseHandler componentDatabaseHandler, PackageDatabaseHandler packageDatabaseHandler, AttachmentDatabaseHandler attachmentDatabaseHandler) throws MalformedURLException { super(attachmentDatabaseHandler); DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(httpClient, dbName); @@ -166,6 +176,7 @@ public ProjectDatabaseHandler(Supplier httpClient, String dbName relUsageRepository = new RelationsUsageRepository(db); vendorRepository = new VendorRepository(db); releaseRepository = new ReleaseRepository(db, vendorRepository); + packageRepository = new PackageRepository(db); // Create the moderator this.moderator = moderator; @@ -174,6 +185,7 @@ public ProjectDatabaseHandler(Supplier httpClient, String dbName attachmentConnector = new AttachmentConnector(httpClient, attachmentDbName, Duration.durationOf(30, TimeUnit.SECONDS)); this.componentDatabaseHandler = componentDatabaseHandler; + this.packageDatabaseHandler = packageDatabaseHandler; DatabaseConnectorCloudant dbChangelogs = new DatabaseConnectorCloudant(httpClient, DatabaseSettings.COUCH_DB_CHANGE_LOGS); this.dbHandlerUtil = new DatabaseHandlerUtil(dbChangelogs); } @@ -359,7 +371,7 @@ public AddDocumentRequestSummary addProject(Project project, User user) throws S return addDocumentRequestSummary; } - if (!isDependenciesExists(project, user)) { + if (!isDependenciesExists(project, user) || (SW360Constants.IS_PACKAGE_PORTLET_ENABLED && isLinkedReleasesUpdateFromLinkedPackagesFailed(project, Collections.emptySet()))) { return new AddDocumentRequestSummary() .setRequestStatus(AddDocumentRequestStatus.INVALID_INPUT); } @@ -417,7 +429,8 @@ public RequestStatus updateProject(Project project, User user, boolean forceUpda return RequestStatus.CLOSED_UPDATE_NOT_ALLOWED; } else if (!changePassesSanityCheck(project, actual)){ return RequestStatus.FAILED_SANITY_CHECK; - } else if (!isDependenciesExists(project, user)) { + } else if (!isDependenciesExists(project, user) || (SW360Constants.IS_PACKAGE_PORTLET_ENABLED && + isLinkedReleasesUpdateFromLinkedPackagesFailed(project, CommonUtils.nullToEmptySet(actual.getPackageIds())))) { return RequestStatus.INVALID_INPUT; } else if (isWriteActionAllowedOnProject(actual, user) || forceUpdate) { copyImmutableFields(project,actual); @@ -518,6 +531,11 @@ private boolean isDependenciesExists(Project project, User user) { isValidDependentIds = DatabaseHandlerUtil.isAllIdInSetExists(Sets.newHashSet(project.getVendorId()), vendorRepository); } + + if (isValidDependentIds && project.isSetPackageIds()) { + Set pacakgeIds = project.getPackageIds(); + isValidDependentIds = DatabaseHandlerUtil.isAllIdInSetExists(pacakgeIds, packageRepository); + } return isValidDependentIds; } @@ -724,6 +742,81 @@ private void updateProjectDependentLinkedFields(Project updated, Project actual) } } + /** + * Link the Release of newly linked Packages to the Project. + * Unlink the Release of unlinked Packages from the Project. + * @return true if linking or unlinking is failed, else returns false. + */ + private boolean isLinkedReleasesUpdateFromLinkedPackagesFailed(Project updatedProject, Set currentPackageIds) throws SW360Exception { + Set updatedPackageIds = CommonUtils.nullToEmptySet(updatedProject.getPackageIds()); + if (updatedPackageIds.equals(currentPackageIds)) { + return false; + } + Set linkedPacakgeIds = Sets.difference(updatedPackageIds, currentPackageIds); + Set unlinkedPacakgeIds = Sets.difference(currentPackageIds, updatedPackageIds); + final ProjectReleaseRelationship releaseRelation = new ProjectReleaseRelationship(ReleaseRelationship.UNKNOWN, MainlineState.OPEN); + if (CommonUtils.isNotEmpty(linkedPacakgeIds)) { + try { + PackageService.Iface packageClient = new ThriftClients().makePackageClient(); + List addedPackages = packageClient.getPackageByIds(linkedPacakgeIds); + + Map releaseIdToUsageMap = addedPackages.stream().map(Package::getReleaseId) + .filter(CommonUtils::isNotNullEmptyOrWhitespace) + .map(relId -> new AbstractMap.SimpleEntry<>(relId, releaseRelation)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal)); + + /* Check if the releaseId found from linked Packages are present in database. + * If present, then link the Release to the Project. + * If not present, then return true (status failed). + */ + if (DatabaseHandlerUtil.isAllIdInSetExists(releaseIdToUsageMap.keySet(), releaseRepository)) { + Map targetMap = CommonUtils.nullToEmptyMap(updatedProject.getReleaseIdToUsage()); + if (CommonUtils.isNullOrEmptyMap(targetMap)) { + updatedProject.setReleaseIdToUsage(releaseIdToUsageMap); + } else { + for (Map.Entry entry : releaseIdToUsageMap.entrySet()) { + targetMap.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + } else { + return true; + } + } catch (TException e) { + log.error(String.format("Error fetching newly added linked package info of project: %s", updatedProject.getId()), e.getCause()); + throw new SW360Exception(e.getMessage()); + } + } + + if (CommonUtils.isNotEmpty(unlinkedPacakgeIds)) { + try { + PackageService.Iface packageClient = new ThriftClients().makePackageClient(); + List removedPackages = packageClient.getPackageWithReleaseByPackageIds(unlinkedPacakgeIds); + + Map> releaseIdToPackageIdsMap = removedPackages.stream().map(Package::getRelease) + .filter(rel -> CommonUtils.isNotEmpty(rel.getPackageIds())) + .map(rel -> new AbstractMap.SimpleEntry<>(rel.getId(), rel.getPackageIds())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal)); + + Map targetMap = CommonUtils.nullToEmptyMap(updatedProject.getReleaseIdToUsage()); + + /* If Project contains releaseId of unlinked Package, + * & check if Project contains at least one Package from the Release of unlinked Package. + * If No, unlink the Release from Project. + */ + for (Map.Entry> entry : releaseIdToPackageIdsMap.entrySet()) { + if (targetMap.containsKey(entry.getKey()) && + CommonUtils.isNotEmpty(CommonUtils.nullToEmptySet(Sets.intersection(entry.getValue(), unlinkedPacakgeIds)))) { + targetMap.remove(entry.getKey()); + } + } + } catch (TException e) { + log.error(String.format("Error fetching removed linked package info of project: %s", updatedProject.getId()), e.getCause()); + throw new SW360Exception(e.getMessage()); + } + } + return false; + } + private boolean changePassesSanityCheck(Project updated, Project current) { return !(nullToEmptyMap(current.getReleaseIdToUsage()).size() > DELETION_SANITY_CHECK_THRESHOLD && nullToEmptyMap(updated.getReleaseIdToUsage()).size() == 0 || @@ -790,6 +883,18 @@ public boolean checkIfInUse(String projectId) { return !usingProjects.isEmpty(); } + public Set searchByPackageId(String id, User user) { + return repository.searchByPackageId(id, user); + } + + public Set searchByPackageIds(Set ids, User user) { + return repository.searchByPackageIds(ids, user); + } + + public int getProjectCountByPackageId(String packageId) { + return repository.getCountByPackageId(packageId); + } + private void removeProjectAndCleanUp(Project project, User user) throws SW360Exception { attachmentConnector.deleteAttachments(project.getAttachments()); attachmentDatabaseHandler.deleteUsagesBy(Source.projectId(project.getId())); @@ -1096,7 +1201,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); @@ -1696,6 +1801,38 @@ 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, packageDatabaseHandler, attachmentConnector, user); + return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user); + } + } catch (IOException e) { + log.error("Exception while parsing 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); @@ -1778,7 +1915,7 @@ private void flattenClearingStatusForReleases(Map releaseIdToRelationship = rel.getReleaseIdToRelationship(); releaseOrigin.put(releaseId, SW360Utils.printName(rel)); @@ -1808,7 +1945,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)); @@ -1870,7 +2007,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/ProjectRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectRepository.java index e4129f4a48..700ac2d0cf 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/ProjectRepository.java @@ -221,6 +221,15 @@ public class ProjectRepository extends SummaryAwareRepository { " }" + "}"; + private static final String BY_PACKAGE_ID_VIEW = + "function(doc) {" + + " if (doc.type == 'project' && doc.packageIds) {" + + " for(var i in doc.packageIds) {" + + " emit(doc.packageIds[i], doc._id);" + + " }" + + " }" + + "}"; + private static final String BY_LINKING_PROJECT_ID_VIEW = "function(doc) {" + " if (doc.type == 'project') {" + @@ -261,6 +270,7 @@ public ProjectRepository(DatabaseConnectorCloudant db) { views.put("bytype", createMapReduce(BY_TYPE_VIEW, null)); views.put("byState", createMapReduce(BY_STATE_VIEW, null)); views.put("byreleaseid", createMapReduce(BY_RELEASE_ID_VIEW, null)); + views.put("byPackageId", createMapReduce(BY_PACKAGE_ID_VIEW, null)); views.put("fullbyreleaseid", createMapReduce(FULL_BY_RELEASE_ID_VIEW, null)); views.put("bylinkingprojectid", createMapReduce(BY_LINKING_PROJECT_ID_VIEW, null)); views.put("fullmyprojects", createMapReduce(FULL_MY_PROJECTS_VIEW, null)); @@ -302,6 +312,20 @@ public int getCountByReleaseIds(Set ids) { return searchIds.size(); } + public Set searchByPackageId(String id, User user) { + return searchByPackageIds(Collections.singleton(id), user); + } + + public Set searchByPackageIds(Set ids, User user) { + Set projectIds = queryForIdsAsValue("byPackageId", ids); + return getAccessibleProjectSummary(user, projectIds); + } + + public int getCountByPackageId(String id) { + Set projectIds = queryForIdsAsValue("byPackageId", Collections.singleton(id)); + return projectIds.size(); + } + public Set searchByReleaseId(String id) { Set projectIds = queryForIdsAsValue("fullbyreleaseid", id); return getFullDocsById(projectIds); 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..b1434ef288 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 @@ -37,8 +37,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) {" + @@ -46,38 +46,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 {" + @@ -131,19 +131,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)); initStandardDesignDocument(views, db); } @@ -151,10 +151,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-common/src/test/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandlerTest.java b/backend/src-common/src/test/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandlerTest.java index 1b74943dba..3990aa2b8e 100644 --- a/backend/src-common/src/test/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandlerTest.java +++ b/backend/src-common/src/test/java/org/eclipse/sw360/datahandler/db/ProjectDatabaseHandlerTest.java @@ -49,7 +49,7 @@ public class ProjectDatabaseHandlerTest { private static final String dbName = DatabaseSettingsTest.COUCH_DB_DATABASE; private static final String attachmentsDbName = DatabaseSettingsTest.COUCH_DB_ATTACHMENTS; - private static final String changeLogsDbName = DatabaseSettingsTest.COUCH_CHANGELOGS; + private static final String changeLogsDbName = DatabaseSettingsTest.COUCH_DB_CHANGELOGS; private static final User user1 = new User().setEmail("user1").setDepartment("AB CD EF"); private static final User user2 = new User().setEmail("user2").setDepartment("AB CD FE"); @@ -60,6 +60,8 @@ public class ProjectDatabaseHandlerTest { ProjectDatabaseHandler handler; ComponentDatabaseHandler componentHandler; AttachmentDatabaseHandler attachmentDatabaseHandler; + PackageDatabaseHandler packageHandler; + @Before public void setUp() throws Exception { assertTestString(dbName); @@ -122,7 +124,8 @@ public void setUp() throws Exception { componentHandler = new ComponentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogsDbName, attachmentsDbName); attachmentDatabaseHandler = new AttachmentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentsDbName); - handler = new ProjectDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogsDbName, attachmentsDbName, moderator, componentHandler, attachmentDatabaseHandler); + packageHandler = new PackageDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogsDbName, attachmentsDbName, attachmentDatabaseHandler, componentHandler); + handler = new ProjectDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogsDbName, attachmentsDbName, moderator, componentHandler, packageHandler, attachmentDatabaseHandler); } @After diff --git a/backend/src/pom.xml b/backend/src/pom.xml index b616515909..6ba4500e59 100644 --- a/backend/src/pom.xml +++ b/backend/src/pom.xml @@ -46,6 +46,7 @@ src-spdxdocument src-spdxdocumentcreationinfo src-spdxpackageinfo + src-packages diff --git a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/ComponentHandlerTest.java b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/ComponentHandlerTest.java index 54ae18eb37..4e32c19fbe 100644 --- a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/ComponentHandlerTest.java +++ b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/ComponentHandlerTest.java @@ -45,7 +45,7 @@ public void setUp() throws Exception { deleteAllDatabases(); componentHandler = new ComponentHandler(DatabaseSettingsTest.getConfiguredClient(), DatabaseSettingsTest.getConfiguredHttpClient(), DatabaseSettingsTest.COUCH_DB_DATABASE, - DatabaseSettingsTest.COUCH_CHANGELOGS, DatabaseSettingsTest.COUCH_DB_ATTACHMENTS); + DatabaseSettingsTest.COUCH_DB_CHANGELOGS, DatabaseSettingsTest.COUCH_DB_ATTACHMENTS); } @After diff --git a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ComponentDatabaseHandlerTest.java b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ComponentDatabaseHandlerTest.java index cc720c6ce7..b0760eeea3 100644 --- a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ComponentDatabaseHandlerTest.java +++ b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ComponentDatabaseHandlerTest.java @@ -60,7 +60,7 @@ public class ComponentDatabaseHandlerTest { private static final String dbName = DatabaseSettingsTest.COUCH_DB_DATABASE; private static final String attachmentsDbName = DatabaseSettingsTest.COUCH_DB_ATTACHMENTS; - private static final String changeLogsDbName = DatabaseSettingsTest.COUCH_CHANGELOGS; + private static final String changeLogsDbName = DatabaseSettingsTest.COUCH_DB_CHANGELOGS; private static final String email1 = "cedric.bodet@tngtech.com"; private static final String email2 = "johannes.najjar@tngtech.com"; diff --git a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ProjectDatabaseHandlerTest.java b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ProjectDatabaseHandlerTest.java index e905534091..5180369493 100644 --- a/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ProjectDatabaseHandlerTest.java +++ b/backend/src/src-components/src/test/java/org/eclipse/sw360/components/db/ProjectDatabaseHandlerTest.java @@ -18,6 +18,7 @@ import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.db.AttachmentDatabaseHandler; import org.eclipse.sw360.datahandler.db.ComponentDatabaseHandler; +import org.eclipse.sw360.datahandler.db.PackageDatabaseHandler; import org.eclipse.sw360.datahandler.db.ProjectDatabaseHandler; import org.eclipse.sw360.datahandler.entitlement.ProjectModerator; import org.eclipse.sw360.datahandler.thrift.MainlineState; @@ -60,6 +61,7 @@ public class ProjectDatabaseHandlerTest { private static final String dbName = DatabaseSettingsTest.COUCH_DB_DATABASE; private static final String attachmentsDbName = DatabaseSettingsTest.COUCH_DB_ATTACHMENTS; + private static final String changeLogsDbName = DatabaseSettingsTest.COUCH_DB_CHANGELOGS; @Rule public final ExpectedException exception = ExpectedException.none(); @@ -80,6 +82,7 @@ public class ProjectDatabaseHandlerTest { public void setUp() throws Exception { assertTestString(dbName); assertTestString(attachmentsDbName); + assertTestString(changeLogsDbName); vendors = new ArrayList<>(); vendors.add(new Vendor().setId("V1").setShortname("vendor").setFullname("vendor").setUrl("http://vendor.example.com")); @@ -138,7 +141,8 @@ public void setUp() throws Exception { ComponentDatabaseHandler componentHandler = new ComponentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentsDbName); AttachmentDatabaseHandler attachmentDatabaseHandler = new AttachmentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentsDbName); - handler = new ProjectDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentsDbName, moderator, componentHandler, attachmentDatabaseHandler); + PackageDatabaseHandler packageHandler = new PackageDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogsDbName, attachmentsDbName, attachmentDatabaseHandler, componentHandler); + handler = new ProjectDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentsDbName, moderator, componentHandler, packageHandler, attachmentDatabaseHandler); } private ProjectReleaseRelationship newDefaultProjectReleaseRelationship() { diff --git a/backend/src/src-packages/pom.xml b/backend/src/src-packages/pom.xml new file mode 100644 index 0000000000..dd596b0a7c --- /dev/null +++ b/backend/src/src-packages/pom.xml @@ -0,0 +1,25 @@ + + + + + + backend-src + org.eclipse.sw360 + ${revision} + + 4.0.0 + + src-packages + jar + src-packages + diff --git a/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageHandler.java b/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageHandler.java new file mode 100644 index 0000000000..0ece1240d5 --- /dev/null +++ b/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageHandler.java @@ -0,0 +1,155 @@ +/* + * 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.packages; + +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertId; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotEmpty; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertUser; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.common.DatabaseSettings; +import org.eclipse.sw360.datahandler.db.PackageDatabaseHandler; +import org.eclipse.sw360.datahandler.db.PackageSearchHandler; +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.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; +import org.eclipse.sw360.datahandler.thrift.users.User; + +/** + * Implementation of the Thrift service + * + * @author abdul.kapti@siemens-healthineers.com + */ +public class PackageHandler implements PackageService.Iface { + + private final PackageDatabaseHandler handler; + private final PackageSearchHandler packageSearchHandler; + + PackageHandler() throws IOException { + handler = new PackageDatabaseHandler(DatabaseSettings.getConfiguredClient(), DatabaseSettings.COUCH_DB_DATABASE, DatabaseSettings.COUCH_DB_CHANGE_LOGS, DatabaseSettings.COUCH_DB_ATTACHMENTS); + packageSearchHandler = new PackageSearchHandler(DatabaseSettings.getConfiguredHttpClient(), DatabaseSettings.getConfiguredClient(), DatabaseSettings.COUCH_DB_DATABASE); + } + + @Override + public Package getPackageById(String packageId) throws SW360Exception { + assertId(packageId); + return handler.getPackageById(packageId); + } + + @Override + public List getPackageWithReleaseByPackageIds(Set ids) throws SW360Exception { + assertNotEmpty(ids); + return handler.getPackageWithReleaseByPackageIds(ids); + } + + @Override + public List getPackageByIds(Set ids) throws SW360Exception { + assertNotEmpty(ids); + return handler.getPackageByIds(ids); + } + + @Override + public List getAllPackages() throws TException { + return handler.getAllPackages(); + } + + @Override + public List getAllOrphanPackages() throws TException { + return handler.getAllOrphanPackages(); + } + + @Override + public List searchPackages(String text, User user) throws TException { + assertUser(user); + assertNotEmpty(text, "package search text cannot be empty"); + return handler.searchPackages(packageSearchHandler, text); + } + + @Override + public List searchOrphanPackages(String text, User user) throws TException { + assertUser(user); + assertNotEmpty(text, "orphan package search text cannot be empty"); + return handler.searchOrphanPackages(packageSearchHandler, text); + } + + @Override + public Set getPackagesByReleaseId(String id) throws TException { + assertNotEmpty(id); + return handler.getPackagesByReleaseId(id); + } + + @Override + public Set getPackagesByReleaseIds(Set ids) throws TException { + assertNotEmpty(ids); + return handler.getPackagesByReleaseIds(ids); + } + + @Override + public AddDocumentRequestSummary addPackage(Package pkg, User user) throws TException { + assertNotNull(pkg); + assertUser(user); + return handler.addPackage(pkg, user); + } + + @Override + public RequestStatus updatePackage(Package pkg, User user) throws SW360Exception { + assertNotNull(pkg); + assertUser(user); + return handler.updatePackage(pkg, user); + } + + @Override + public RequestStatus updatePackageWithForceFlag(Package pkg, User user, boolean forceUpdate) throws TException { + assertNotNull(pkg); + assertId(pkg.getId()); + assertUser(user); + + return handler.updatePackage(pkg, user, forceUpdate); + } + + @Override + public RequestStatus deletePackage(String packageId, User user) throws TException { + assertId(packageId); + assertUser(user); + return handler.deletePackage(packageId, user); + } + + @Override + public RequestStatus deletePackageWithForceFlag(String id, User user, boolean forceDelete) throws TException { + assertId(id); + assertUser(user); + + return handler.deletePackage(id, user, forceDelete); + } + + @Override + public Map> getPackagesWithPagination(PaginationData pageData) throws TException { + return handler.getPackagesWithPagination(pageData); + } + + @Override + public List searchPackagesWithFilter(String text, Map> subQueryRestrictions) throws TException { + return handler.searchPackagesWithFilter(text, packageSearchHandler, subQueryRestrictions); + } + + @Override + public int getTotalPackagesCount() { + return handler.getTotalPackageCount(); + } +} \ No newline at end of file diff --git a/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageServlet.java b/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageServlet.java new file mode 100644 index 0000000000..7aeaa55aa9 --- /dev/null +++ b/backend/src/src-packages/src/main/java/org/eclipse/sw360/packages/PackageServlet.java @@ -0,0 +1,34 @@ +/* + * 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.packages; + +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; +import org.eclipse.sw360.projects.Sw360ThriftServlet; +import org.apache.thrift.protocol.TCompactProtocol; + +import java.io.IOException; +import java.net.MalformedURLException; + + +/** + * Thrift Servlet instantiation + * + * @author abdul.kapti@siemens-healthineers.com + */ +public class PackageServlet extends Sw360ThriftServlet { + + private static final long serialVersionUID = 1L; + + public PackageServlet() throws MalformedURLException, IOException { + // Create a service processor using the provided handler + super(new PackageService.Processor<>(new PackageHandler()), new TCompactProtocol.Factory()); + } + +} 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 0a849eb005..840175aaa5 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 @@ -10,6 +10,12 @@ */ package org.eclipse.sw360.projects; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertId; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertIdUnset; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotEmpty; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull; +import static org.eclipse.sw360.datahandler.common.SW360Assert.assertUser; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; @@ -20,8 +26,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 +36,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; @@ -42,8 +47,6 @@ import java.util.*; import java.util.function.Supplier; -import static org.eclipse.sw360.datahandler.common.SW360Assert.*; - /** * Implementation of the Thrift service * @@ -158,6 +161,26 @@ public Set searchByReleaseIds(Set ids, User user) throws TExcep return handler.searchByReleaseId(ids, user); } + @Override + public Set searchProjectByPackageId(String id, User user) throws TException { + assertId(id); + assertUser(user); + return handler.searchByPackageId(id, user); + } + + @Override + public Set searchProjectByPackageIds(Set ids, User user) throws TException { + assertNotEmpty(ids); + assertUser(user); + return handler.searchByPackageIds(ids, user); + } + + @Override + public int getProjectCountByPackageId(String id) throws TException { + assertNotEmpty(id); + return handler.getProjectCountByPackageId(id); + } + @Override public Set searchLinkingProjects(String id, User user) throws TException { assertId(id); @@ -236,6 +259,19 @@ 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 String getSbomImportInfoFromAttachmentAsString(String attachmentContentId) throws SW360Exception { + assertId(attachmentContentId); + return handler.getSbomImportInfoFromAttachmentAsString(attachmentContentId); + } + //////////////////////////// // ADD INDIVIDUAL OBJECTS // //////////////////////////// diff --git a/backend/src/src-projects/src/test/java/org/eclipse/sw360/projects/ProjectHandlerTest.java b/backend/src/src-projects/src/test/java/org/eclipse/sw360/projects/ProjectHandlerTest.java index ceb8920dda..3534e2e938 100644 --- a/backend/src/src-projects/src/test/java/org/eclipse/sw360/projects/ProjectHandlerTest.java +++ b/backend/src/src-projects/src/test/java/org/eclipse/sw360/projects/ProjectHandlerTest.java @@ -17,6 +17,7 @@ import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.db.AttachmentDatabaseHandler; import org.eclipse.sw360.datahandler.db.ComponentDatabaseHandler; +import org.eclipse.sw360.datahandler.db.PackageDatabaseHandler; import org.eclipse.sw360.datahandler.db.ProjectDatabaseHandler; import org.eclipse.sw360.datahandler.entitlement.ProjectModerator; import org.eclipse.sw360.datahandler.thrift.RequestStatus; @@ -46,7 +47,7 @@ public class ProjectHandlerTest { private static final String dbName = DatabaseSettingsTest.COUCH_DB_DATABASE; private static final String attachmentDbName = DatabaseSettingsTest.COUCH_DB_ATTACHMENTS; - private static final String changeLogDbName = DatabaseSettingsTest.COUCH_CHANGELOGS; + private static final String changeLogDbName = DatabaseSettingsTest.COUCH_DB_CHANGELOGS; private static final User user1 = new User().setEmail("user1").setDepartment("AB CD EF"); private static final User user2 = new User().setEmail("user2").setDepartment("AB CD FE"); @@ -225,6 +226,7 @@ public void testUpdateProject2_1() throws Exception { ProjectDatabaseHandler handler = new ProjectDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogDbName, attachmentDbName, moderator, new ComponentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentDbName), + new PackageDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, changeLogDbName, attachmentDbName), new AttachmentDatabaseHandler(DatabaseSettingsTest.getConfiguredClient(), dbName, attachmentDbName)); Project project2 = handler.getProjectById("P2", user1); project2.setName("Project2new"); diff --git a/backend/src/src-search/src/main/java/org/eclipse/sw360/search/db/AbstractDatabaseSearchHandler.java b/backend/src/src-search/src/main/java/org/eclipse/sw360/search/db/AbstractDatabaseSearchHandler.java index 1c8a45ea25..32e528bae4 100644 --- a/backend/src/src-search/src/main/java/org/eclipse/sw360/search/db/AbstractDatabaseSearchHandler.java +++ b/backend/src/src-search/src/main/java/org/eclipse/sw360/search/db/AbstractDatabaseSearchHandler.java @@ -162,7 +162,7 @@ public List restrictedSearch(String text, List typeMask, U typeField.add("title"); query = "( " + Joiner.on(" OR ").join(FluentIterable.from(typeField).transform(addField)) + " ) "; } else { - if (typeMask.contains("project") || typeMask.contains("component") || typeMask.contains("release")) { + if (typeMask.contains("project") || typeMask.contains("component") || typeMask.contains("release") || typeMask.contains("package")) { typeField.add("name"); } if (typeMask.contains("license") || typeMask.contains("user") || typeMask.contains("vendor")) { diff --git a/backend/svc/pom.xml b/backend/svc/pom.xml index a0c9640687..942413dcb9 100644 --- a/backend/svc/pom.xml +++ b/backend/svc/pom.xml @@ -45,6 +45,7 @@ svc-spdxdocument svc-spdxdocumentcreationinfo svc-spdxpackageinfo + svc-packages diff --git a/backend/svc/svc-packages/pom.xml b/backend/svc/svc-packages/pom.xml new file mode 100644 index 0000000000..9aedd211ae --- /dev/null +++ b/backend/svc/svc-packages/pom.xml @@ -0,0 +1,70 @@ + + + + + + backend-svc + org.eclipse.sw360 + ${revision} + + 4.0.0 + + svc-packages + war + svc-packages + + + ${backend.deploy.dir} + + + + packages + + + + maven-dependency-plugin + + + add-build-configuration-resources + generate-resources + + unpack + + + + + org.eclipse.sw360 + src-${project.build.finalName} + ${project.version} + jar + true + ${project.build.outputDirectory} + **/*.java,**/*.class + + + + + + + + + + + + org.eclipse.sw360 + src-${project.build.finalName} + ${project.version} + jar + + + diff --git a/backend/svc/svc-packages/src/main/webapp/WEB-INF/web.xml b/backend/svc/svc-packages/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..201cc3d7db --- /dev/null +++ b/backend/svc/svc-packages/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,34 @@ + + + + Packages Service + + index.jsp + + + + org.eclipse.sw360.SW360ServiceContextListener + + + + PackageService + org.eclipse.sw360.packages.PackageServlet + 1 + + + PackageService + /thrift + + + \ No newline at end of file diff --git a/backend/svc/svc-packages/src/main/webapp/index.jsp b/backend/svc/svc-packages/src/main/webapp/index.jsp new file mode 100644 index 0000000000..050c8d6c1d --- /dev/null +++ b/backend/svc/svc-packages/src/main/webapp/index.jsp @@ -0,0 +1,22 @@ + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Welcome to the SW360 Packages Service + + +

Welcome to the SW360 Packages Service!

+ + diff --git a/frontend/liferay-theme/src/main/webapp/css/sw360/components/_typeicon.scss b/frontend/liferay-theme/src/main/webapp/css/sw360/components/_typeicon.scss index 2004811209..f8e4cf5765 100644 --- a/frontend/liferay-theme/src/main/webapp/css/sw360/components/_typeicon.scss +++ b/frontend/liferay-theme/src/main/webapp/css/sw360/components/_typeicon.scss @@ -20,6 +20,9 @@ .type-icon-release { color: #FF7F27; } +.type-icon-package { + color: #FF1493; +} .type-icon-oblig { color: #009E09; } 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/ErrorMessages.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java index 3dd6060320..8fbab3ba34 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java @@ -9,6 +9,8 @@ */ package org.eclipse.sw360.portal.common; +import org.eclipse.sw360.datahandler.common.SW360Constants; + import com.google.common.collect.ImmutableList; /** @@ -35,6 +37,7 @@ public class ErrorMessages { public static final String ERROR_GETTING_RELEASE = "Error fetching release from backend."; public static final String LICENSE_USED_BY_RELEASE = "Request could not be processed, as license is used by at least one release!"; public static final String DOCUMENT_USED_BY_PROJECT_OR_RELEASE = "Document could not be processed, as it is used by other Projects or Releases!"; + public static final String DOCUMENT_USED_BY_PROJECT_OR_RELEASE_OR_PACKAGE = "Document could not be processed, as it is used by other Projects or Releases or Packages!"; public static final String DOCUMENT_NOT_PROCESSED_SUCCESSFULLY = "Document could not be processed."; public static final String FIRST_NAME_CANNOT_BE_EMPTY= "First name cannot be empty."; public static final String LAST_NAME_CANNOT_BE_EMPTY = "Last name cannot be empty."; @@ -65,7 +68,12 @@ public class ErrorMessages { public static final String OBLIGATION_NOT_UPDATED = "Obligation could not be updated."; public static final String VENDOR_DUPLICATE = "A vendor with the same name already exists."; public static final String ERROR_VENDOR = "Error: Invalid vendor Name or Url."; - + public static final String PACKAGE_NOT_ADDED = "Package could not be added."; + public static final String PACKAGE_DUPLICATE = "A package with the same name and version already exists."; + public static final String PACKAGE_NAME_VERSION_ERROR = "Name and version of the package cannot contain only space characters."; + public static final String PACKAGE_UPDATE_ACCESS_DENIED = "You do not have permission to update this Package, Package creator or all users with " + SW360Constants.PACKAGE_PORTLET_WRITE_ACCESS_USER_ROLE.name() + " & above role can update the Packages."; + public static final String INVALID_PURL_OR_LINKED_RELEASE = "Invalid purl or linked release id"; + public static final String INVALID_LINKED_DOCUMENT = "Invalid linked document id"; public static final String ERROR_VULNERABILITY_USED_BY_RELEASE = "Can not remove vulnerability because it is used by releases"; public static final String ERROR_VULNERABILITY_DELETE = "Error when delete vulnerability"; @@ -87,6 +95,7 @@ public class ErrorMessages { .add(DUPLICATE_ATTACHMENT) .add(LICENSE_USED_BY_RELEASE) .add(DOCUMENT_USED_BY_PROJECT_OR_RELEASE) + .add(DOCUMENT_USED_BY_PROJECT_OR_RELEASE_OR_PACKAGE) .add(DOCUMENT_NOT_PROCESSED_SUCCESSFULLY) .add(DEFAULT_ERROR_MESSAGE) .add(FIRST_NAME_CANNOT_BE_EMPTY) @@ -127,6 +136,12 @@ public class ErrorMessages { .add(ERROR_UPDATE_VULNERABILITY) .add(VENDOR_DUPLICATE) .add(ERROR_VENDOR) + .add(PACKAGE_NOT_ADDED) + .add(PACKAGE_DUPLICATE) + .add(PACKAGE_NAME_VERSION_ERROR) + .add(PACKAGE_UPDATE_ACCESS_DENIED) + .add(INVALID_PURL_OR_LINKED_RELEASE) + .add(INVALID_LINKED_DOCUMENT) .build(); private ErrorMessages() { 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..8a6bf1e5e0 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,8 +26,8 @@ * @author stefan.jaeger@evosoft.com * @author alex.borodin@evosoft.com */ -public class PortalConstants { +public class PortalConstants { public static final String PROPERTIES_FILE_PATH = "/sw360.properties"; public static final String PROGRAMMING_LANGUAGES; public static final Set DOMAIN; @@ -108,6 +108,22 @@ public class PortalConstants { public static final String IS_PROJECT_MEMBER = "isProjectMember"; public static final String IS_ERROR_IN_UPDATE_OR_CREATE = "isErrorInUpdateOrCreate"; + //! Specialized keys for packages + public static final String PACKAGE = "Package"; + public static final String PKG = "pkg"; + public static final String PACKAGES = "packages"; + public static final String PACKAGES_PORTLET_NAME = PORTLET_NAME_PREFIX + PACKAGES; + public static final String PACKAGE_ID = "packageId"; + public static final String PACKAGE_IDS = "packageIds"; + public static final String IMPORT_CYCLONEDX_SBOM = "importCycloneDxSBoM"; + public static final String PACKAGE_LIST = "packageList"; + public static final String LOAD_PACKAGE_LIST = "loadPackageList"; + public static final String DELETE_PACKAGE = "delete_package"; + public static final String PAGENAME_PACKAGE_DETAIL = "detailPackage"; + public static final String PAGENAME_EDIT_PACKAGE = "editPackage"; + public static final String LOAD_RELEASE_INFO = "loadReleaseInfo"; + public static final String ORPHAN_PACKAGE_CHECKBOX = "orphanPackageCheckBox"; + //! Specialized keys for licenses public static final String LICENSES_PORTLET_NAME = PORTLET_NAME_PREFIX + "licenses"; public static final String KEY_LICENSE_DETAIL = "licenseDetail"; @@ -268,15 +284,19 @@ public class PortalConstants { public static final String USING_COMPONENTS = "usingComponents"; public static final String USING_RELEASES = "usingReleases"; public static final String ALL_USING_PROJECTS_COUNT = "allUsingProjectsCount"; + public static final String USING_RELEASE = "usingRelease"; public static final String PROJECT_LIST = "projectList"; public static final String ALL_SUB_PROJECT_LINK = "allSubProjectLink"; public static final String RELEASE_LIST = "releaseList"; public static final String TOTAL_INACCESSIBLE_ROWS = "totalInaccessibleRows"; public static final String PROJECT_SEARCH = "projectSearch"; public static final String RELEASE_SEARCH = "releaseSearch"; + public static final String PACKAGE_SEARCH = "packageSearch"; 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_LINKED_PACKAGES = "loadLinkedPackages"; + public static final String LOAD_SBOM_IMPORT_INFO = "loadSbomImportInfo"; public static final String RELEASE_LIST_FROM_LINKED_PROJECTS = "releaseListFromLinkedProjects"; public static final String STATE; @@ -474,6 +494,7 @@ public class PortalConstants { //! Keys for Search public static final String TYPE_MASK = "typeMask"; public static final String SEARCH_PORTLET_NAME = PORTLET_NAME_PREFIX + "search"; + public static final String IS_SEARCH_TRUNCATED = "isSearchTruncated"; //! Keys for Preferences public static final String PREFERENCES_PORTLET_NAME = PORTLET_NAME_PREFIX + "preferences"; @@ -551,6 +572,8 @@ public class PortalConstants { public static final String SAVE_ATTACHMENT_USAGES = "save_attachment_usages"; public static final String PARENT_BRANCH_ID = "parent_branch_id"; public static final String PARENT_SCOPE_GROUP_ID = "parentScopeGroupId"; + public static final String ADD_LINKED_PACKAGES = "addLinkedPackages"; + public static final String VIEW_LINKED_PACKAGES = "viewLinkedPackages"; // bom import public static final String PREPARE_IMPORT_BOM = "prepareImportBom"; @@ -558,6 +581,7 @@ public class PortalConstants { 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"; // project actions public static final String VIEW_LINKED_PROJECTS = "view_linked_projects"; @@ -577,6 +601,7 @@ public class PortalConstants { public static final String PROJECT_CHECK_FOR_ATTACHMENTS = "verifyAttachmentExistance"; public static final String LICENSE_TO_SOURCE_FILE = "licenseToSourceFile"; public static final String ADD_LICENSE_TO_RELEASE = "addLicenseToRelease"; + public static final String UPDATED_RELEASE_BY_LINKED_PACKAGES = "updateReleaseByLinkedPackages"; //component actions public static final String ADD_VENDOR = "add_vendor"; 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..cb873483da 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 @@ -33,6 +33,8 @@ import org.eclipse.sw360.datahandler.thrift.cvesearch.VulnerabilityUpdateStatus; import org.eclipse.sw360.datahandler.thrift.licenses.ObligationLevel; import org.eclipse.sw360.datahandler.thrift.licenses.ObligationType; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageManagerType; import org.eclipse.sw360.datahandler.thrift.licenses.Obligation; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectClearingState; @@ -150,6 +152,10 @@ public static ObligationType getObligationTypeFromString(String enumNumber) { return ObligationType.findByValue(parseInt(enumNumber)); } + public static PackageManagerType getPackageManagerTypeFromString(String enumNumber) { + return PackageManagerType.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 +211,8 @@ else if (field == Obligation._Fields.OBLIGATION_LEVEL) return getObligationLevelFromString(value); else if (field == Obligation._Fields.OBLIGATION_TYPE) return getObligationTypeFromString(value); + else if (field == Package._Fields.PACKAGE_MANAGER_TYPE) + return getPackageManagerTypeFromString(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/LinkToPortletConfiguration.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkToPortletConfiguration.java index 1a4aa1d8a5..af32156153 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkToPortletConfiguration.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkToPortletConfiguration.java @@ -20,6 +20,7 @@ import org.eclipse.sw360.portal.portlets.components.ComponentPortlet; import org.eclipse.sw360.portal.portlets.licenses.LicensesPortlet; import org.eclipse.sw360.portal.portlets.moderation.ModerationPortlet; +import org.eclipse.sw360.portal.portlets.packages.PackagePortlet; import org.eclipse.sw360.portal.portlets.projects.ProjectPortlet; import org.eclipse.sw360.portal.portlets.vulnerabilities.VulnerabilitiesPortlet; @@ -40,7 +41,8 @@ public enum LinkToPortletConfiguration { LICENSES(LicensesPortlet.class), MODERATION(ModerationPortlet.class), VULNERABILITIES(VulnerabilitiesPortlet.class), - USERS(UserPortlet.class); + USERS(UserPortlet.class), + PACKAGES(PackagePortlet.class); private final Class portletClass; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkedReleasesAndProjectsAwarePortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkedReleasesAndProjectsAwarePortlet.java index 15f11b4467..ce760364a4 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkedReleasesAndProjectsAwarePortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/LinkedReleasesAndProjectsAwarePortlet.java @@ -20,6 +20,8 @@ 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.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectLink; import org.eclipse.sw360.datahandler.thrift.projects.ProjectRelationship; @@ -28,6 +30,11 @@ import org.eclipse.sw360.portal.common.PortalConstants; import org.eclipse.sw360.portal.users.UserCacheHolder; +import com.google.common.collect.Lists; +import com.liferay.portal.kernel.json.JSONArray; +import com.liferay.portal.kernel.json.JSONFactoryUtil; +import com.liferay.portal.kernel.json.JSONObject; + import javax.portlet.PortletException; import javax.portlet.PortletRequest; import javax.portlet.ResourceRequest; @@ -35,7 +42,6 @@ import java.io.IOException; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -46,7 +52,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static com.google.common.base.Strings.isNullOrEmpty; +import static com.liferay.portal.kernel.json.JSONFactoryUtil.createJSONArray; import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyList; import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyString; import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; @@ -114,6 +120,49 @@ protected void putDirectlyLinkedReleaseRelationsInRequest(PortletRequest request request.setAttribute(RELEASE_LIST, linkedReleaseRelations); } + protected void putDirectlyLinkedPackagesInRequest(PortletRequest request, Set packageIds) { + List linkedPackages = Lists.newArrayList(); + if (CommonUtils.isNotEmpty(packageIds)) { + try { + PackageService.Iface client = thriftClients.makePackageClient(); + linkedPackages.addAll(client.getPackageByIds(packageIds)); + linkedPackages = linkedPackages.stream() + .sorted(Comparator.comparing(pkg -> SW360Utils.getVersionedName(nullToEmptyString(pkg.getName()), pkg.getVersion()), + String.CASE_INSENSITIVE_ORDER)) + .collect(Collectors.toList()); + } catch (TException e) { + log.error("Could not get linked packages", e); + } + } + request.setAttribute(PortalConstants.PACKAGE_LIST, linkedPackages); + } + + protected JSONArray getPackageData(List packages, User user) { + JSONArray packageData = createJSONArray(); + for (Package pkg : packages) { + JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); + jsonObject.put("id", pkg.getId()); + jsonObject.put("DT_RowId", pkg.getId()); + jsonObject.put("name", SW360Utils.printName(pkg)); + final String relId = pkg.getReleaseId(); + jsonObject.put("relId", CommonUtils.nullToEmptyString(relId)); + if (Objects.nonNull(pkg.getRelease())) { + Release rel = pkg.getRelease(); + jsonObject.put("relName", SW360Utils.printName(rel)); + jsonObject.put("relCS", ThriftEnumUtils.enumToString(rel.getClearingState())); + } + JSONArray licenseArray = createJSONArray(); + if (pkg.isSetLicenseIds()) { + pkg.getLicenseIds().stream().sorted().forEach(licenseArray::put); + } + jsonObject.put("lics", licenseArray); + jsonObject.put("pkgMgrType", ThriftEnumUtils.enumToString(pkg.getPackageManagerType())); + jsonObject.put("writeAccess", SW360Utils.isWriteAccessUser(pkg, user)); + packageData.put(jsonObject); + } + return packageData; + } + protected void putDirectlyLinkedReleaseRelationsWithAccessibilityInRequest(PortletRequest request, Release release, User user) { List linkedReleaseRelations = SW360Utils.getLinkedReleaseRelationsWithAccessibility(release, thriftClients, log, user); linkedReleaseRelations = linkedReleaseRelations.stream().filter(Objects::nonNull).sorted(Comparator.comparing( diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java index 85e0157d23..3958694ba6 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java @@ -25,11 +25,15 @@ import com.liferay.portal.kernel.util.PortalUtil; import com.liferay.portal.kernel.util.ResourceBundleUtil; +import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.thrift.*; +import org.eclipse.sw360.datahandler.common.SW360Constants; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.licenses.LicenseService; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserService; @@ -62,10 +66,12 @@ import javax.portlet.*; import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.sw360.portal.common.PortalConstants.PACKAGE_LIST; abstract public class Sw360Portlet extends MVCPortlet { private static final int MAX_LENGTH_USERS_IN_DISPLAY = 100; + private static final int MAX_LENGTH_PACKAGES_TO_DISPLAY = 100; protected static final Logger log = LogManager.getLogger(Sw360Portlet.class); protected final ThriftClients thriftClients; @@ -222,8 +228,9 @@ public List limitLengthOfUserList(List userList) { public void dealWithUserAction(ResourceRequest request, ResourceResponse response, String action) throws IOException, PortletException { if (PortalConstants.USER_SEARCH.equals(action)) { - String searchText = request.getParameter(PortalConstants.WHAT); - String how = request.getParameter(PortalConstants.HOW); + ResourceParameters parameters = request.getResourceParameters(); + String searchText = parameters.getValue(PortalConstants.WHAT); + String how = parameters.getValue(PortalConstants.HOW); Boolean multiUsers = false; if (!isNullOrEmpty(how)) { @@ -257,7 +264,7 @@ public Boolean isListTruncated(List fullList, List truncatedList) { public void dealWithLicenseAction(ResourceRequest request, ResourceResponse response, String action) throws IOException, PortletException { if (PortalConstants.LICENSE_SEARCH.equals(action)) { - final String searchText = request.getParameter(PortalConstants.WHAT); + final String searchText = request.getResourceParameters().getValue(PortalConstants.WHAT); try { LicenseService.Iface client = thriftClients.makeLicenseClient(); @@ -325,6 +332,8 @@ public void setSessionMessage(PortletRequest request, RequestStatus requestStatu case IN_USE: if (type.equals("License")) { setSW360SessionError(request, ErrorMessages.LICENSE_USED_BY_RELEASE); + } else if (SW360Constants.IS_PACKAGE_PORTLET_ENABLED) { + setSW360SessionError(request, ErrorMessages.DOCUMENT_USED_BY_PROJECT_OR_RELEASE_OR_PACKAGE); } else { setSW360SessionError(request, ErrorMessages.DOCUMENT_USED_BY_PROJECT_OR_RELEASE); } @@ -338,6 +347,8 @@ public void setSessionMessage(PortletRequest request, RequestStatus requestStatu case DUPLICATE: case DUPLICATE_ATTACHMENT: case NAMINGERROR: + case ACCESS_DENIED: + case INVALID_INPUT: // just break to not throw an exception, error message has to be set by caller // because of type specific error messages break; @@ -412,6 +423,103 @@ protected void serveReleaseSearch(ResourceRequest request, ResourceResponse resp include("/html/utils/ajax/searchReleasesAjax.jsp", request, response, PortletRequest.RESOURCE_PHASE); } + protected void serveLinkedPackages(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + ResourceParameters parameters = request.getResourceParameters(); + String what = parameters.getValue(PortalConstants.WHAT); + + if (PortalConstants.ADD_LINKED_PACKAGES.equals(what)) { + String[] where = parameters.getValues(PortalConstants.WHERE_ARRAY); + serveNewTableRowLinkedPackage(request, response, where); + } else if (PortalConstants.PACKAGE_SEARCH.equals(what)) { + String where = parameters.getValue(PortalConstants.WHERE); + servePackageSearchResults(request, response, where); + } + } + + protected void serveDeletePackage(ResourceRequest request, ResourceResponse response) throws IOException { + final User user = UserCacheHolder.getUserFromRequest(request); + String packageId = request.getResourceParameters().getValue(PortalConstants.PACKAGE_ID); + RequestStatus requestStatus = deletePackage(packageId, user, log); + serveRequestStatus(request, response, requestStatus, "Problem removing package", log); + } + + protected static RequestStatus deletePackage(String packageId, User user, Logger log) { + if (packageId != null) { + try { + PackageService.Iface client = new ThriftClients().makePackageClient(); + return client.deletePackage(packageId, user); + } catch (TException e) { + log.error(String.format("Error removing package %s from DB", packageId), e); + } + } + return RequestStatus.FAILURE; + } + + protected void serveNewTableRowLinkedPackage(ResourceRequest request, ResourceResponse response, String[] linkedIds) throws IOException, PortletException { + List linkedPackages = new ArrayList<>(); + try { + if (linkedIds != null && CommonUtils.isNotNullEmptyOrWhitespace(linkedIds[0])) { + PackageService.Iface client = thriftClients.makePackageClient(); + linkedPackages.addAll(client.getPackageByIds(new HashSet<>(Arrays.asList(linkedIds)))); + } + } catch (TException e) { + log.error("Error getting packages!", e); + throw new PortletException("cannot get packages " + Arrays.toString(linkedIds), e); + } + request.setAttribute(PACKAGE_LIST, linkedPackages); + include("/html/utils/ajax/linkedPackagesAjax.jsp", request, response, PortletRequest.RESOURCE_PHASE); + } + + protected void servePackageSearchResults(ResourceRequest request, ResourceResponse response, String searchText) throws IOException, PortletException { + servePackageSearch(request, response, searchText); + } + + protected void servePackageSearch(ResourceRequest request, ResourceResponse response, String searchText) throws IOException, PortletException { + final User user = UserCacheHolder.getUserFromRequest(request); + String portletName = CommonUtils.nullToEmptyString((String) request.getAttribute("PORTLET_ID")); + List searchResult = new ArrayList<>(); + if (portletName.endsWith("components")) { + searchResult = servePackageListBySearchTextForRelease(searchText, user); + } else { + searchResult = servePackageListBySearchTextForProject(searchText, user); + } + request.setAttribute(PortalConstants.IS_SEARCH_TRUNCATED, false); + if (searchResult.size() > MAX_LENGTH_PACKAGES_TO_DISPLAY) { + searchResult.subList(0, MAX_LENGTH_PACKAGES_TO_DISPLAY); + request.setAttribute(PortalConstants.IS_SEARCH_TRUNCATED, true); + } + request.setAttribute(PortalConstants.PACKAGE_SEARCH, searchResult); + include("/html/utils/ajax/searchPackagesAjax.jsp", request, response, PortletRequest.RESOURCE_PHASE); + } + + private List servePackageListBySearchTextForProject(String searchText, User user) { + try { + PackageService.Iface packageClient = thriftClients.makePackageClient(); + if (isNullOrEmpty(searchText)) { + return packageClient.getAllPackages(); + } else { + return packageClient.searchPackages(searchText, user); + } + } catch (TException e) { + log.error("Error searching linked packages for projects", e); + return Collections.emptyList(); + } + } + + private List servePackageListBySearchTextForRelease(String searchText, User user) { + try { + PackageService.Iface packageClient = thriftClients.makePackageClient(); + if (isNullOrEmpty(searchText)) { + return packageClient.getAllOrphanPackages(); + } else { + return packageClient.searchOrphanPackages(searchText, user); + } + } catch (TException e) { + log.error("Error searching linked packages for release", e); + return Collections.emptyList(); + } + } + protected List serveReleaseListBySearchText(String searchText, User user) { try { ComponentService.Iface componentClient = thriftClients.makeComponentClient(); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java index 9d73b6f361..a21589c891 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java @@ -46,7 +46,6 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentService; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentUsage; -import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.cvesearch.CveSearchService; import org.eclipse.sw360.datahandler.thrift.cvesearch.VulnerabilityUpdateStatus; @@ -55,6 +54,8 @@ import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoParsingResult; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoService; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseNameWithText; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.spdx.annotations.Annotations; @@ -108,7 +109,6 @@ import static org.eclipse.sw360.datahandler.common.WrappedException.wrapTException; import static org.eclipse.sw360.portal.common.PortalConstants.*; import static org.eclipse.sw360.portal.common.PortletUtils.getVerificationState; -import static org.eclipse.sw360.portal.common.PortletUtils.setDepartmentSearchAttribute; import org.apache.thrift.transport.TTransportException; import org.apache.thrift.protocol.TType; @@ -222,6 +222,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th serveUnsubscribeRelease(request, response); } else if (PortalConstants.VIEW_LINKED_RELEASES.equals(action)) { serveLinkedReleases(request, response); + } else if (PortalConstants.VIEW_LINKED_PACKAGES.equals(action)) { + serveLinkedPackages(request, response); } else if (PortalConstants.PROJECT_SEARCH.equals(action)) { serveProjectSearch(request, response); } else if (PortalConstants.UPDATE_VULNERABILITIES_RELEASE.equals(action)){ @@ -252,6 +254,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th serveLicenseToSourceFileMapping(request, response); } else if (PortalConstants.PREPARE_IMPORT_BOM.equals(action)) { prepareImportBom(request, response); + } else if (PortalConstants.LOAD_LINKED_PACKAGES.equals(action)) { + loadLinkedPackages(request, response); } else if (isGenericAction(action)) { dealWithGenericAction(request, response, action); } else if (PortalConstants.LOAD_CHANGE_LOGS.equals(action) || PortalConstants.VIEW_CHANGE_LOGS.equals(action)) { @@ -262,6 +266,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th writeJSON(request, response, dataForChangeLogs); } else if (PortalConstants.EVALUATE_CLI_ATTACHMENTS.equals(action)) { evaluateCLIAttachments(request, response); + } else if (PortalConstants.DELETE_PACKAGE.equals(action)) { + serveDeletePackage(request, response); } } @@ -287,7 +293,7 @@ private void exportExcelWithEmail(ResourceRequest request, ResourceResponse resp pageData.setDisplayStart(displayStart); pageData.setRowsPerPage(rowsPerPage); displayStart = displayStart + rowsPerPage; - pageDtToProjects = getFilteredComponentList(request, pageData); + pageDtToProjects = getFilteredComponentList(request, pageData, null); projects.addAll(pageDtToProjects.entrySet().iterator().next().getValue()); total = total - rowsPerPage; } @@ -446,6 +452,33 @@ private void prepareImportBom(ResourceRequest request, ResourceResponse response } } + private void loadLinkedPackages(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + ResourceParameters parameters = request.getResourceParameters(); + String releaseId = parameters.getValue(PortalConstants.DOCUMENT_ID); + if (CommonUtils.isNotNullEmptyOrWhitespace(releaseId)) { + final User user = UserCacheHolder.getUserFromRequest(request); + final ComponentService.Iface client = thriftClients.makeComponentClient(); + Release release; + try { + release = client.getReleaseById(releaseId, user); + if (CommonUtils.isNotEmpty(release.getPackageIds())) { + final PackageService.Iface packageClient = thriftClients.makePackageClient(); + List packages = packageClient.getPackageByIds(release.getPackageIds()); + JSONArray packagesData = getPackageData(packages, user); + final JSONObject jsonResult = createJSONObject(); + jsonResult.put("data", packagesData); + try { + writeJSON(request, response, jsonResult); + } catch (IOException e) { + log.error("Problem converting linked packages to JSON! ", e); + } + } + } catch (TException e) { + log.error("Problem fetching release from db: " + releaseId, e); + } + } + } + private void serveCheckComponentName(ResourceRequest request, ResourceResponse response) throws IOException { List resultComponents = new ArrayList<>(); List errors = new ArrayList<>(); @@ -579,7 +612,7 @@ private void exportExcel(ResourceRequest request, ResourceResponse response) { pageData.setDisplayStart(displayStart); pageData.setRowsPerPage(rowsPerPage); displayStart = displayStart + rowsPerPage; - pageDtToProjects = getFilteredComponentList(request, pageData); + pageDtToProjects = getFilteredComponentList(request, pageData, null); projects.addAll(pageDtToProjects.entrySet().iterator().next().getValue()); total = total - rowsPerPage; } @@ -1018,6 +1051,7 @@ private void prepareReleaseEdit(RenderRequest request, RenderResponse response) setAttachmentsInRequest(request, release); putDirectlyLinkedReleaseRelationsWithAccessibilityInRequest(request, release, user); + putDirectlyLinkedPackagesInRequest(request, release.getPackageIds()); Map permissions = release.getPermissions(); DocumentState documentState = release.getDocumentState(); setUsingDocs(request, releaseId, user, client); @@ -1058,6 +1092,7 @@ private void prepareReleaseEdit(RenderRequest request, RenderResponse response) release.setVendor(component.getDefaultVendor()); request.setAttribute(RELEASE, release); putDirectlyLinkedReleaseRelationsWithAccessibilityInRequest(request, release, user); + putDirectlyLinkedPackagesInRequest(request, release.getPackageIds()); setAttachmentsInRequest(request, release); setUsingDocs(request, null, user, client); SessionMessages.add(request, "request_processed", LanguageUtil.get(resourceBundle,"new.license")); @@ -1242,6 +1277,7 @@ private void prepareReleaseDuplicate(RenderRequest request, RenderResponse respo request.setAttribute(RELEASE_LIST, Collections.emptyList()); request.setAttribute(TOTAL_INACCESSIBLE_ROWS, 0); setUsingDocs(request, null, user, client); + putDirectlyLinkedPackagesInRequest(request, Collections.emptySet()); release.unsetExternalIds(); request.setAttribute(RELEASE, release); request.setAttribute(PortalConstants.ATTACHMENTS, Collections.emptySet()); @@ -2190,27 +2226,6 @@ private Map> getComponentFilterMap(PortletRequest request) { return filterMap; } - private List getFilteredComponentList(PortletRequest request) { - Map> filterMap = getComponentFilterMap(request); - List componentList; - int limit = -1; - - try { - final User user = UserCacheHolder.getUserFromRequest(request); - ComponentService.Iface componentClient = thriftClients.makeComponentClient(); - if (filterMap.isEmpty()) { - componentList = componentClient.getAccessibleRecentComponentsSummary(limit, user); - } else { - componentList = componentClient.refineSearchAccessibleComponents(null, filterMap, user); - } - } catch (TException e) { - log.error("Could not search components in backend ", e); - componentList = Collections.emptyList(); - } - - return componentList; - } - //! Actions @UsedAsLiferayAction public void updateComponent(ActionRequest request, ActionResponse response) throws PortletException, IOException { @@ -2434,6 +2449,7 @@ private void prepareRequestForReleaseEditAfterDuplicateError(ActionRequest reque request.setAttribute(RELEASE, release); setAttachmentsInRequest(request, release); putDirectlyLinkedReleaseRelationsInRequest(request, release); + putDirectlyLinkedPackagesInRequest(request, release.getPackageIds()); request.setAttribute(USING_PROJECTS, Collections.emptySet()); request.setAttribute(USING_COMPONENTS, Collections.emptySet()); request.setAttribute(ALL_USING_PROJECTS_COUNT, 0); @@ -2569,9 +2585,8 @@ private void serveComponentList(ResourceRequest request, ResourceResponse respon sortParam = paginationParameters.getSortingColumn().get(); } pageData.setSortColumnNumber(sortParam); - - Map> pageDataComponentList = getFilteredComponentList(request, pageData); Map> filterMap = getComponentFilterMap(request); + Map> pageDataComponentList = getFilteredComponentList(request, pageData, filterMap); JSONArray jsonComponents = getComponentData(pageDataComponentList.values().iterator().next(), paginationParameters, filterMap); JSONObject jsonResult = createJSONObject(); jsonResult.put(DATATABLE_RECORDS_TOTAL, pageDataComponentList.keySet().iterator().next().getTotalRowCount()); @@ -2586,10 +2601,12 @@ private void serveComponentList(ResourceRequest request, ResourceResponse respon } } - private Map> getFilteredComponentList(PortletRequest request, PaginationData pageData) { - Map> filterMap = getComponentFilterMap(request); + private Map> getFilteredComponentList(PortletRequest request, PaginationData pageData, Map> filterMap) { List componentList; Map> pageDataComponents = Maps.newHashMap(); + if (filterMap == null) { + filterMap = getComponentFilterMap(request); + } try { final User user = UserCacheHolder.getUserFromRequest(request); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortletUtils.java index 4256e0b33c..6590839676 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortletUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortletUtils.java @@ -85,6 +85,9 @@ public static void updateReleaseFromRequest(PortletRequest request, Release rele case ADDITIONAL_DATA: release.setAdditionalData(PortletUtils.getAdditionalDataMapFromRequest(request)); break; + case PACKAGE_IDS: + updatePackageIds(request, release); + break; default: setFieldValue(request, release, field); } @@ -100,6 +103,16 @@ private static ClearingInformation getClearingInformationFromRequest(PortletRequ return clearingInformation; } + private static void updatePackageIds(PortletRequest request, Release release) { + release.unsetPackageIds(); + String[] ids = request.getParameterValues(PortalConstants.PACKAGE_IDS); + if (ids != null && ids.length > 0) { + for (int k = 0; k < ids.length; ++k) { + release.addToPackageIds(ids[k]); + } + } + } + private static EccInformation getEccInformationFromRequest(PortletRequest request) { EccInformation eccInformation = newDefaultEccInformation(); for (EccInformation._Fields field : EccInformation._Fields.values()) { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackageFriendlyUrlMapper.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackageFriendlyUrlMapper.java new file mode 100644 index 0000000000..ca70b96c3b --- /dev/null +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackageFriendlyUrlMapper.java @@ -0,0 +1,31 @@ +/* + * 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.portal.portlets.packages; + +import com.liferay.portal.kernel.portlet.DefaultFriendlyURLMapper; +import com.liferay.portal.kernel.portlet.FriendlyURLMapper; + +import org.osgi.service.component.annotations.Component; + +import static org.eclipse.sw360.portal.common.PortalConstants.PACKAGES_PORTLET_NAME; + +@Component( + property = { + "com.liferay.portlet.friendly-url-routes=org/eclipse/sw360/portal/mapper/package-friendly-url-routes.xml", + "javax.portlet.name=" + PACKAGES_PORTLET_NAME, + }, + service = FriendlyURLMapper.class + ) +public class PackageFriendlyUrlMapper extends DefaultFriendlyURLMapper { + @Override + public String getMapping() { + return "package"; + } +} diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortlet.java new file mode 100644 index 0000000000..69b974f268 --- /dev/null +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortlet.java @@ -0,0 +1,586 @@ +/* + * 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.portal.portlets.packages; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.base.Strings.nullToEmpty; +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 org.eclipse.sw360.datahandler.common.CommonUtils.isNotNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptySet; +import static org.eclipse.sw360.datahandler.common.SW360Utils.printName; +import static org.eclipse.sw360.portal.common.PortalConstants.ALL_USING_PROJECTS_COUNT; +import static org.eclipse.sw360.portal.common.PortalConstants.DATATABLE_DISPLAY_DATA; +import static org.eclipse.sw360.portal.common.PortalConstants.DATATABLE_RECORDS_FILTERED; +import static org.eclipse.sw360.portal.common.PortalConstants.DATATABLE_RECORDS_TOTAL; +import static org.eclipse.sw360.portal.common.PortalConstants.DOCUMENT_ID; +import static org.eclipse.sw360.portal.common.PortalConstants.IS_ERROR_IN_UPDATE_OR_CREATE; +import static org.eclipse.sw360.portal.common.PortalConstants.PACKAGE; +import static org.eclipse.sw360.portal.common.PortalConstants.PACKAGES_PORTLET_NAME; +import static org.eclipse.sw360.portal.common.PortalConstants.PACKAGE_ID; +import static org.eclipse.sw360.portal.common.PortalConstants.PAGENAME; +import static org.eclipse.sw360.portal.common.PortalConstants.PAGENAME_DETAIL; +import static org.eclipse.sw360.portal.common.PortalConstants.PAGENAME_EDIT; +import static org.eclipse.sw360.portal.common.PortalConstants.PAGENAME_VIEW; +import static org.eclipse.sw360.portal.common.PortalConstants.PKG; +import static org.eclipse.sw360.portal.common.PortalConstants.RELEASE; +import static org.eclipse.sw360.portal.common.PortalConstants.USING_PROJECTS; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.portlet.ActionParameters; +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.MutableRenderParameters; +import javax.portlet.Portlet; +import javax.portlet.PortletException; +import javax.portlet.PortletRequest; +import javax.portlet.PortletURL; +import javax.portlet.RenderParameters; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; +import javax.portlet.ResourceParameters; +import javax.portlet.ResourceRequest; +import javax.portlet.ResourceResponse; +import javax.servlet.http.HttpServletRequest; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.thrift.TException; +import org.eclipse.sw360.commonIO.SampleOptions; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.common.ThriftEnumUtils; +import org.eclipse.sw360.datahandler.couchdb.lucene.LuceneAwareDatabaseConnector; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; +import org.eclipse.sw360.datahandler.thrift.DateRange; +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.components.ComponentService; +import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; +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.portal.common.ChangeLogsPortletUtils; +import org.eclipse.sw360.portal.common.ErrorMessages; +import org.eclipse.sw360.portal.common.PortalConstants; +import org.eclipse.sw360.portal.common.PortletUtils; +import org.eclipse.sw360.portal.common.UsedAsLiferayAction; +import org.eclipse.sw360.portal.common.datatables.PaginationParser; +import org.eclipse.sw360.portal.common.datatables.data.PaginationParameters; +import org.eclipse.sw360.portal.portlets.Sw360Portlet; +import org.eclipse.sw360.portal.portlets.projects.ProjectPortlet; +import org.eclipse.sw360.portal.users.UserCacheHolder; +import org.osgi.service.component.annotations.ConfigurationPolicy; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.liferay.portal.kernel.json.JSONArray; +import com.liferay.portal.kernel.json.JSONFactoryUtil; +import com.liferay.portal.kernel.json.JSONObject; +import com.liferay.portal.kernel.servlet.SessionMessages; +import com.liferay.portal.kernel.util.PortalUtil; + +@org.osgi.service.component.annotations.Component( + immediate = true, + properties = { + "/org/eclipse/sw360/portal/portlets/base.properties", + "/org/eclipse/sw360/portal/portlets/default.properties" + }, property = { + "javax.portlet.name=" + PACKAGES_PORTLET_NAME, + "javax.portlet.display-name=Packages", + "javax.portlet.info.short-title=Packages", + "javax.portlet.info.title=Packages", + "javax.portlet.resource-bundle=content.Language", + "javax.portlet.init-param.view-template=/html/packages/view.jsp", + }, + service = Portlet.class, + configurationPolicy = ConfigurationPolicy.REQUIRE +) +public class PackagePortlet extends Sw360Portlet { + + private static final Logger log = LogManager.getLogger(ProjectPortlet.class); + + private static final ImmutableList packageFilteredFields = ImmutableList.of( + Package._Fields.NAME, + Package._Fields.VERSION, + Package._Fields.PACKAGE_MANAGER_TYPE, + Package._Fields.LICENSE_IDS, + Package._Fields.CREATED_BY, + Package._Fields.CREATED_ON); + + // Package view data tables, index of columns + private static final int PACKAGE_NO_SORT = -1; + private static final int PACKAGE_DT_ROW_NAME = 0; + private static final int PACKAGE_DT_ROW_LICENSES = 3; + private static final int PACKAGE_DT_ROW_PACKAGE_MANAGER_TYPE = 4; + + // Helper methods + private void addPackageBreadcrumb(RenderRequest request, RenderResponse response, Package pkg) { + PortletURL url = response.createRenderURL(); + MutableRenderParameters parameters = url.getRenderParameters(); + parameters.setValue(PAGENAME, PAGENAME_DETAIL); + parameters.setValue(PACKAGE_ID, pkg.getId()); + addBreadcrumbEntry(request, pkg.getName(), url); + } + + // ! Serve resource and helpers + @Override + public void serveResource(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + + final var action = request.getResourceParameters().getValue(PortalConstants.ACTION); + if (PortalConstants.LOAD_PACKAGE_LIST.equals(action)) { + servePackageList(request, response); + } else if (isGenericAction(action)) { + dealWithGenericAction(request, response, action); + } else if (PortalConstants.VIEW_LINKED_RELEASES.equals(action)) { + serveLinkedReleases(request, response); + } else if (PortalConstants.LOAD_CHANGE_LOGS.equals(action) || PortalConstants.VIEW_CHANGE_LOGS.equals(action)) { + ChangeLogsPortletUtils changeLogsPortletUtilsPortletUtils = PortletUtils.getChangeLogsPortletUtils(thriftClients); + JSONObject dataForChangeLogs = changeLogsPortletUtilsPortletUtils.serveResourceForChangeLogs(request, response, action); + writeJSON(request, response, dataForChangeLogs); + } else if (PortalConstants.DELETE_PACKAGE.equals(action)) { + serveDeletePackage(request, response); + } + } + + // ! VIEW and helpers + @Override + public void doView(RenderRequest request, RenderResponse response) throws IOException, PortletException { + + final var pageName = request.getRenderParameters().getValue(PAGENAME); + if (PAGENAME_DETAIL.equals(pageName)) { + prepareDetailView(request, response); + include("/html/packages/detailPackage.jsp", request, response); + } else if (PAGENAME_EDIT.equals(pageName)) { + preparePackageEdit(request); + include("/html/packages/editPackage.jsp", request, response); + } else { + prepareStandardView(request); + super.doView(request, response); + } + } + + private void serveLinkedReleases(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + ResourceParameters parameters = request.getResourceParameters(); + String what = parameters.getValue(PortalConstants.WHAT); + if (PortalConstants.RELEASE_SEARCH.equals(what)) { + String where = parameters.getValue(PortalConstants.WHERE); + serveReleaseSearchResults(request, response, where); + } + } + + private void serveReleaseSearchResults(ResourceRequest request, ResourceResponse response, String searchText) throws IOException, PortletException { + request.setAttribute("isSingleRelease", "true"); + serveReleaseSearch(request, response, searchText); + } + + private void prepareStandardView(RenderRequest request) { + RenderParameters parameters = request.getRenderParameters(); + for (Package._Fields filteredField : packageFilteredFields) { + String parameter = parameters.getValue(filteredField.toString()); + request.setAttribute(filteredField.getFieldName(), nullToEmpty(parameter)); + } + request.setAttribute(PortalConstants.EXACT_MATCH_CHECKBOX, nullToEmpty(parameters.getValue(PortalConstants.EXACT_MATCH_CHECKBOX))); + request.setAttribute(PortalConstants.ORPHAN_PACKAGE_CHECKBOX, nullToEmpty(parameters.getValue(PortalConstants.ORPHAN_PACKAGE_CHECKBOX))); + request.setAttribute(PortalConstants.DATE_RANGE, nullToEmpty(parameters.getValue(PortalConstants.DATE_RANGE))); + request.setAttribute(PortalConstants.END_DATE, nullToEmpty(parameters.getValue(PortalConstants.END_DATE))); + } + + private void servePackageList(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + HttpServletRequest originalServletRequest = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(request)); + PaginationParameters paginationParameters = PaginationParser.parametersFrom(originalServletRequest); + handlePaginationSortOrder(request, paginationParameters); + 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(); + } + + pageData.setSortColumnNumber(sortParam); + Map> filterMap = getPackageFilterMap(request); + Map> pageDataPackageList = getFilteredPackageList(request, pageData, filterMap); + JSONArray jsonPackages = getPackageData(request, pageDataPackageList.values().iterator().next(), paginationParameters, filterMap); + JSONObject jsonResult = createJSONObject(); + jsonResult.put(DATATABLE_RECORDS_TOTAL, pageDataPackageList.keySet().iterator().next().getTotalRowCount()); + jsonResult.put(DATATABLE_RECORDS_FILTERED, pageDataPackageList.keySet().iterator().next().getTotalRowCount()); + jsonResult.put(DATATABLE_DISPLAY_DATA, jsonPackages); + + try { + writeJSON(request, response, jsonResult); + } catch (IOException e) { + log.error("An error ocured while writing Package list to JSON: ", e); + } + } + + private void handlePaginationSortOrder(ResourceRequest request, PaginationParameters paginationParameters) { + if (!paginationParameters.getSortingColumn().isPresent()) { + RenderParameters parameters = request.getRenderParameters(); + for (Package._Fields filteredField : packageFilteredFields) { + if (!isNullOrEmpty(parameters.getValue(filteredField.toString()))) { + paginationParameters.setSortingColumn(Optional.of(PACKAGE_NO_SORT)); + break; + } + } + } + } + + private Map> getFilteredPackageList(PortletRequest request, PaginationData pageData, Map> filterMap) { + List packageList; + Map> pageDataPackages = Maps.newHashMap(); + + try { + PackageService.Iface packageClient = thriftClients.makePackageClient(); + if (filterMap.isEmpty()) { + pageDataPackages = packageClient.getPackagesWithPagination(pageData); + } else { + packageList = packageClient.searchPackagesWithFilter(null, filterMap); + pageDataPackages.put(pageData.setTotalRowCount(packageList.size()), packageList); + } + } catch (TException e) { + log.error("Could not search packages in backend ", e); + pageDataPackages = Collections.emptyMap(); + } + + return pageDataPackages; + } + + private Map> getPackageFilterMap(ResourceRequest request) { + Map> filterMap = new HashMap<>(); + RenderParameters parameters = request.getRenderParameters(); + String exactMatch = parameters.getValue(PortalConstants.EXACT_MATCH_CHECKBOX); + String orphanSearch = parameters.getValue(PortalConstants.ORPHAN_PACKAGE_CHECKBOX); + for (Package._Fields filteredField : packageFilteredFields) { + String parameter = parameters.getValue(filteredField.toString()); + if (!isNullOrEmpty(parameter) && !(filteredField.equals(Package._Fields.PACKAGE_MANAGER_TYPE) && parameter.equals(PortalConstants.NO_FILTER))) { + if (filteredField.equals(Package._Fields.CREATED_ON) && isNotNullEmptyOrWhitespace(parameters.getValue(PortalConstants.DATE_RANGE))) { + Date date = new Date(); + String upperLimit = new SimpleDateFormat(SampleOptions.DATE_OPTION).format(date); + String dateRange = parameters.getValue(PortalConstants.DATE_RANGE); + String query = new StringBuilder("[%s ").append(PortalConstants.TO).append(" %s]").toString(); + DateRange range = ThriftEnumUtils.stringToEnum(dateRange, DateRange.class); + switch (range) { + case EQUAL: + break; + case LESS_THAN_OR_EQUAL_TO: + parameter = String.format(query, PortalConstants.EPOCH_DATE, parameter); + break; + case GREATER_THAN_OR_EQUAL_TO: + parameter = String.format(query, parameter, upperLimit); + break; + case BETWEEN: + String endDate = parameters.getValue(PortalConstants.END_DATE); + if (isNullEmptyOrWhitespace(endDate)) { + endDate = upperLimit; + } + parameter = String.format(query, parameter, endDate); + break; + } + } + Set values = CommonUtils.splitToSet(parameter); + if (filteredField.equals(Package._Fields.NAME) || filteredField.equals(Package._Fields.VERSION)) { + if (!exactMatch.isEmpty() && !(parameter.startsWith("\"") && parameter.endsWith("\""))) { + values = values.stream().map(s -> "\"" + s + "\"").map(LuceneAwareDatabaseConnector::prepareWildcardQuery).collect(Collectors.toSet()); + } + else { + values = values.stream().map(LuceneAwareDatabaseConnector::prepareWildcardQuery).collect(Collectors.toSet()); + } + } + filterMap.put(filteredField.getFieldName(), values); + } + } + if (CommonUtils.isNotNullEmptyOrWhitespace(orphanSearch)) { + filterMap.put(PortalConstants.ORPHAN_PACKAGE_CHECKBOX, Collections.emptySet()); + } + return filterMap; + } + + public JSONArray getPackageData(ResourceRequest request, List packageList, PaginationParameters packageParameters, Map> filterMap) { + List sortedPackages = sortPackageList(packageList, packageParameters); + int count = getPackageDataCount(packageParameters, packageList.size()); + ComponentService.Iface compClient = thriftClients.makeComponentClient(); + final int start = filterMap.isEmpty() ? 0 : packageParameters.getDisplayStart(); + final User user = UserCacheHolder.getUserFromRequest(request); + + JSONArray packageData = createJSONArray(); + for (int i = start; i < count; i++) { + JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); + Package pkg = sortedPackages.get(i); + jsonObject.put("DT_RowId", pkg.getId()); + jsonObject.put("name", SW360Utils.printName(pkg)); + final String relId = pkg.getReleaseId(); + jsonObject.put("relId", CommonUtils.nullToEmptyString(relId)); + if (CommonUtils.isNotNullEmptyOrWhitespace(relId)) { + try { + Release rel = compClient.getReleaseById(relId, user); + jsonObject.put("relName", SW360Utils.printName(rel)); + jsonObject.put("relCS", ThriftEnumUtils.enumToString(rel.getClearingState())); + } catch (SW360Exception e) { + log.error(String.format("An error occure while getting release <%s> info of a package <%s>", relId, pkg.getId()), e); + } catch (TException e) { + log.error(String.format("An error occure while getting release <%s> info of a package <%s>", relId, pkg.getId()), e); + } + } + JSONArray licenseArray = createJSONArray(); + if (pkg.isSetLicenseIds()) { + pkg.getLicenseIds().stream().sorted().forEach(licenseArray::put); + } + jsonObject.put("lics", licenseArray); + jsonObject.put("pkgMgrType", ThriftEnumUtils.enumToString(pkg.getPackageManagerType())); + jsonObject.put("writeAccess", SW360Utils.isWriteAccessUser(pkg, user)); + + packageData.put(jsonObject); + } + + return packageData; + } + + private int getPackageDataCount(PaginationParameters packageParameters, int maxSize) { + if (packageParameters.getDisplayLength() == -1) { + return maxSize; + } else { + return min(packageParameters.getDisplayStart() + packageParameters.getDisplayLength(), maxSize); + } + } + + private List sortPackageList(List packageList, PaginationParameters packageParameters) { + boolean isAsc = packageParameters.isAscending().orElse(true); + + switch (packageParameters.getSortingColumn().orElse(PACKAGE_DT_ROW_NAME)) { + case PACKAGE_DT_ROW_NAME: + Collections.sort(packageList, compareByName(isAsc)); + break; + case PACKAGE_DT_ROW_LICENSES: + Collections.sort(packageList, compareByLicenses(isAsc)); + break; + case PACKAGE_DT_ROW_PACKAGE_MANAGER_TYPE: + Collections.sort(packageList, compareByPackageManagerType(isAsc)); + break; + default: + break; + } + + return packageList; + } + + private Comparator compareByName(boolean isAscending) { + Comparator nameComparator = Comparator.comparing(p -> SW360Utils.printName(p).toLowerCase()); + return isAscending ? nameComparator : nameComparator.reversed(); + } + + private Comparator compareByLicenses(boolean isAscending) { + Comparator pkgManagerComparator = Comparator.comparing(p -> sortAndConcat(p.getLicenseIds())); + return isAscending ? pkgManagerComparator : pkgManagerComparator.reversed(); + } + + private Comparator compareByPackageManagerType(boolean isAscending) { + Comparator pkgManagerComparator = Comparator.comparing(p -> p.getPackageManagerType().toString()); + return isAscending ? pkgManagerComparator : pkgManagerComparator.reversed(); + } + + private String sortAndConcat(Set strings) { + if (strings == null || strings.isEmpty()) { + return ""; + } + return CommonUtils.COMMA_JOINER.join(strings.stream().sorted(String.CASE_INSENSITIVE_ORDER).collect(Collectors.toList())); + } + + private void prepareDetailView(RenderRequest request, RenderResponse response) { + String id = request.getRenderParameters().getValue(PACKAGE_ID); + request.setAttribute(DOCUMENT_ID, id); + final User user = UserCacheHolder.getUserFromRequest(request); + PackageService.Iface packageClient = thriftClients.makePackageClient(); + Package pkg; + try { + pkg = packageClient.getPackageById(id); + request.setAttribute(PKG, pkg); + Release release = null; + if (CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) { + ComponentService.Iface compClient = thriftClients.makeComponentClient(); + release = compClient.getReleaseById(pkg.getReleaseId(), user); + request.setAttribute(RELEASE, release); + String releaseName = SW360Utils.printFullname(release); + request.setAttribute("releaseName", releaseName); + } + request.setAttribute(PortalConstants.WRITE_ACCESS_USER, SW360Utils.isWriteAccessUser(pkg, user)); + setUsingDocs(request, pkg.getId(), user); + addPackageBreadcrumb(request, response, pkg); + } catch (TException e) { + log.error("An error occured while loading package for detail view: " + id, e); + } + } + + private void preparePackageEdit(RenderRequest request) { + String id = request.getRenderParameters().getValue(PACKAGE_ID); + final User user = UserCacheHolder.getUserFromRequest(request); + PackageService.Iface packageClient = thriftClients.makePackageClient(); + Package pkg = (Package) request.getAttribute(PKG); + final Boolean isPackageAbsent = (pkg == null); + try { + if (id == null) { + if (isPackageAbsent) { + pkg = new Package(); + } + } else { + if (isPackageAbsent) { + pkg = packageClient.getPackageById(id); + } + } + Release release = null; + if (CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) { + ComponentService.Iface compClient = thriftClients.makeComponentClient(); + release = compClient.getReleaseById(pkg.getReleaseId(), user); + request.setAttribute(RELEASE, release); + String releaseName = SW360Utils.printFullname(release); + request.setAttribute("releaseName", releaseName); + } + setUsingDocsCount(request, id, user); + request.setAttribute(PKG, pkg); + } catch (TException e) { + log.error("An error occured while loading package for edit view: " + id, e); + } + } + + @UsedAsLiferayAction + public void updatePackage(ActionRequest request, ActionResponse response) throws PortletException, IOException { + String packageId = request.getActionParameters().getValue(PACKAGE_ID); + User user = UserCacheHolder.getUserFromRequest(request); + PackageService.Iface packageClient = thriftClients.makePackageClient(); + Package pkg; + RequestStatus requestStatus; + try { + MutableRenderParameters parameters = response.getRenderParameters(); + if (packageId != null) { + // Update existing Package + pkg = packageClient.getPackageById(packageId); + PackagePortletUtils.updatePackageFromRequest(request, pkg); + requestStatus = packageClient.updatePackage(pkg, user); + setSessionMessage(request, requestStatus, PACKAGE, "update", printName(pkg)); + if (RequestStatus.DUPLICATE.equals(requestStatus) || RequestStatus.NAMINGERROR.equals(requestStatus) + || RequestStatus.INVALID_INPUT.equals(requestStatus) || RequestStatus.ACCESS_DENIED.equals(requestStatus)) { + if (RequestStatus.ACCESS_DENIED.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.PACKAGE_UPDATE_ACCESS_DENIED); + else if (RequestStatus.DUPLICATE.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.PACKAGE_DUPLICATE); + else if (RequestStatus.INVALID_INPUT.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.INVALID_PURL_OR_LINKED_RELEASE); + else if (RequestStatus.NAMINGERROR.equals(requestStatus)) + setSW360SessionError(request, ErrorMessages.PACKAGE_NAME_VERSION_ERROR); + + parameters.setValue(PAGENAME, PAGENAME_EDIT); + parameters.setValue(PACKAGE_ID, packageId); + prepareRequestForEditAfterError(request, pkg, user); + } else { + parameters.setValue(PAGENAME, PAGENAME_DETAIL); + parameters.setValue(PACKAGE_ID, packageId); + request.setAttribute(PKG, pkg); + } + } else { + // Add new Package + pkg = new Package(); + PackagePortletUtils.updatePackageFromRequest(request, pkg); + AddDocumentRequestSummary summary = packageClient.addPackage(pkg, user); + AddDocumentRequestStatus status = summary.getRequestStatus(); + + if (AddDocumentRequestStatus.DUPLICATE.equals(status) || AddDocumentRequestStatus.NAMINGERROR.equals(status) + || AddDocumentRequestStatus.INVALID_INPUT.equals(status)) { + if (AddDocumentRequestStatus.DUPLICATE.equals(status)) + setSW360SessionError(request, ErrorMessages.PACKAGE_DUPLICATE); + else if (AddDocumentRequestStatus.INVALID_INPUT.equals(status)) + setSW360SessionError(request, ErrorMessages.INVALID_PURL_OR_LINKED_RELEASE); + else if (AddDocumentRequestStatus.NAMINGERROR.equals(status)) + setSW360SessionError(request, ErrorMessages.PACKAGE_NAME_VERSION_ERROR); + + parameters.setValue(PAGENAME, PAGENAME_EDIT); + prepareRequestForEditAfterError(request, pkg, user); + } else if (AddDocumentRequestStatus.SUCCESS.equals(status)) { + String successMsg = "Package " + printName(pkg) + " added successfully"; + SessionMessages.add(request, "request_processed", successMsg); + parameters.setValue(PACKAGE_ID, summary.getId()); + parameters.setValue(PAGENAME, PAGENAME_EDIT); + request.setAttribute(PKG, packageClient.getPackageById(summary.getId())); + } else { + setSW360SessionError(request, ErrorMessages.PACKAGE_NOT_ADDED); + parameters.setValue(PAGENAME, PAGENAME_VIEW); + } + } + } catch (TException e) { + log.error("An error occured while updating the package: " + packageId, e); + } + } + + @UsedAsLiferayAction + public void deletePackage(ActionRequest request, ActionResponse response) throws PortletException, IOException { + User user = UserCacheHolder.getUserFromRequest(request); + String packageId = request.getActionParameters().getValue(PortalConstants.PACKAGE_ID); + RequestStatus requestStatus = deletePackage(packageId, user, log); + setSessionMessage(request, requestStatus, "Package", "delete"); + response.getRenderParameters().setValue(PAGENAME, PAGENAME_VIEW); + } + + @UsedAsLiferayAction + public void applyFilters(ActionRequest request, ActionResponse response) throws PortletException, IOException { + MutableRenderParameters responseParam = response.getRenderParameters(); + ActionParameters requestParam = request.getActionParameters(); + for (Package._Fields componentFilteredField : packageFilteredFields) { + responseParam.setValue(componentFilteredField.toString(), nullToEmpty(requestParam.getValue(componentFilteredField.toString()))); + } + responseParam.setValue(PortalConstants.DATE_RANGE, nullToEmpty(requestParam.getValue(PortalConstants.DATE_RANGE))); + responseParam.setValue(PortalConstants.END_DATE, nullToEmpty(requestParam.getValue(PortalConstants.END_DATE))); + responseParam.setValue(PortalConstants.EXACT_MATCH_CHECKBOX, nullToEmpty(requestParam.getValue(PortalConstants.EXACT_MATCH_CHECKBOX))); + responseParam.setValue(PortalConstants.ORPHAN_PACKAGE_CHECKBOX, nullToEmpty(requestParam.getValue(PortalConstants.ORPHAN_PACKAGE_CHECKBOX))); + } + + + private void setUsingDocs(RenderRequest request, String packageId, User user) throws TException { + Set usingProjects = null; + int allUsingProjectsCount = 0; + if (CommonUtils.isNotNullEmptyOrWhitespace(packageId)) { + ProjectService.Iface projectClient = thriftClients.makeProjectClient(); + usingProjects = projectClient.searchProjectByPackageId(packageId, user); + allUsingProjectsCount = projectClient.getProjectCountByPackageId(packageId); + } + request.setAttribute(USING_PROJECTS, nullToEmptySet(usingProjects)); + request.setAttribute(ALL_USING_PROJECTS_COUNT, allUsingProjectsCount); + } + + private void setUsingDocsCount(RenderRequest request, String packageId, User user) throws TException { + int allUsingProjectsCount = 0; + if (CommonUtils.isNotNullEmptyOrWhitespace(packageId)) { + ProjectService.Iface projectClient = thriftClients.makeProjectClient(); + allUsingProjectsCount = projectClient.getProjectCountByPackageId(packageId); + } + request.setAttribute(ALL_USING_PROJECTS_COUNT, allUsingProjectsCount); + } + + private void prepareRequestForEditAfterError(ActionRequest request, Package pkg, User user) throws TException { + request.setAttribute(PKG, pkg); + request.setAttribute(IS_ERROR_IN_UPDATE_OR_CREATE, true); + } +} diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortletUtils.java new file mode 100644 index 0000000000..bbfc18dfcb --- /dev/null +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/packages/PackagePortletUtils.java @@ -0,0 +1,32 @@ +/* + * 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.portal.portlets.packages; + +import javax.portlet.PortletRequest; + +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.portal.common.PortletUtils; + +public class PackagePortletUtils { + + private PackagePortletUtils() { + // Utility class with only static functions + } + + public static void updatePackageFromRequest(PortletRequest request, Package pkg) { + for (Package._Fields field : Package._Fields.values()) { + setFieldValue(request, pkg, field); + } + } + + private static void setFieldValue(PortletRequest request, Package pkg, Package._Fields field) { + PortletUtils.setFieldValue(request, pkg, field, Package.metaDataMap.get(field), ""); + } +} 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..a99dc16907 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 @@ -46,6 +46,8 @@ import org.eclipse.sw360.datahandler.thrift.licenses.ObligationLevel; import org.eclipse.sw360.datahandler.thrift.licenses.ObligationType; import org.eclipse.sw360.datahandler.thrift.moderation.ModerationService; +import org.eclipse.sw360.datahandler.thrift.packages.Package; +import org.eclipse.sw360.datahandler.thrift.packages.PackageService; import org.eclipse.sw360.datahandler.thrift.projects.*; import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; @@ -204,6 +206,10 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th serveRemoveProject(request, response); } else if (PortalConstants.VIEW_LINKED_RELEASES.equals(action)) { serveLinkedReleases(request, response); + } else if (PortalConstants.VIEW_LINKED_PACKAGES.equals(action)) { + serveLinkedPackages(request, response); + } else if (PortalConstants.LOAD_LINKED_PACKAGES.equals(action)) { + loadLinkedPackages(request, response); } else if (PortalConstants.UPDATE_VULNERABILITIES_PROJECT.equals(action)) { updateVulnerabilitiesProject(request, response); } else if (PortalConstants.UPDATE_VULNERABILITY_RATINGS.equals(action)) { @@ -250,8 +256,12 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th serveLicenseToSourceFileMapping(request, response); } else if (PortalConstants.ADD_LICENSE_TO_RELEASE.equals(action)) { addLicenseToLinkedReleases(request, response); + } else if (PortalConstants.UPDATED_RELEASE_BY_LINKED_PACKAGES.equals(action)) { + updateReleaseByLinkedPackages(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 +440,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 +465,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); @@ -2302,6 +2340,7 @@ private void prepareProjectEdit(RenderRequest request) { putDirectlyLinkedProjectsInRequest(request, project, user); } putDirectlyLinkedReleasesWithAccessibilityInRequest(request, project, user); + putDirectlyLinkedPackagesInRequest(request, project.getPackageIds()); } catch (TException e) { log.error("Could not fetch linked projects or linked releases in projects view.", e); return; @@ -2328,6 +2367,7 @@ private void prepareProjectEdit(RenderRequest request) { } request.setAttribute(PROJECT_LIST, projectlink); putDirectlyLinkedReleasesWithAccessibilityInRequest(request, project, user); + putDirectlyLinkedPackagesInRequest(request, project.getPackageIds()); } catch(TException e) { log.error("Could not put empty linked projects or linked releases in projects view.", e); } @@ -2359,6 +2399,7 @@ private void prepareProjectDuplicate(RenderRequest request) { request.setAttribute(PROJECT, newProject); putDirectlyLinkedProjectsInRequest(request, newProject, user); putDirectlyLinkedReleasesWithAccessibilityInRequest(request, newProject, user); + putDirectlyLinkedPackagesInRequest(request, newProject.getPackageIds()); newProject.unsetId(); request.setAttribute(USING_PROJECTS, Collections.emptySet()); request.setAttribute(ALL_USING_PROJECTS_COUNT, 0); @@ -2372,7 +2413,7 @@ private void prepareProjectDuplicate(RenderRequest request) { PortletUtils.setCustomFieldsEdit(request, user, project); putDirectlyLinkedProjectsInRequest(request, project, user); putDirectlyLinkedReleasesWithAccessibilityInRequest(request, project, user); - + putDirectlyLinkedPackagesInRequest(request, project.getPackageIds()); request.setAttribute(USING_PROJECTS, Collections.emptySet()); request.setAttribute(ALL_USING_PROJECTS_COUNT, 0); } @@ -2575,6 +2616,7 @@ private void prepareRequestForEditAfterDuplicateError(ActionRequest request, Pro request.setAttribute(IS_ERROR_IN_UPDATE_OR_CREATE, true); putDirectlyLinkedProjectsInRequest(request, project, user); putDirectlyLinkedReleasesWithAccessibilityInRequest(request, project, user); + putDirectlyLinkedPackagesInRequest(request, project.getPackageIds()); } @UsedAsLiferayAction @@ -2898,6 +2940,93 @@ private void addLicenseToLinkedReleases(ResourceRequest request, ResourceRespons } } + private void loadLinkedPackages(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + String projectId = request.getResourceParameters().getValue(PortalConstants.DOCUMENT_ID); + if (CommonUtils.isNotNullEmptyOrWhitespace(projectId)) { + final User user = UserCacheHolder.getUserFromRequest(request); + final ProjectService.Iface client = thriftClients.makeProjectClient(); + Project project; + try { + project = client.getProjectById(projectId, user); + if (CommonUtils.isNotEmpty(project.getPackageIds())) { + final PackageService.Iface packageClient = thriftClients.makePackageClient(); + List packages = packageClient.getPackageWithReleaseByPackageIds(project.getPackageIds()); + JSONArray packagesData = getPackageData(packages, user); + final JSONObject jsonResult = createJSONObject(); + jsonResult.put("data", packagesData); + try { + writeJSON(request, response, jsonResult); + } catch (IOException e) { + log.error("Problem converting linked packages to JSON! ", e); + } + } + } catch (TException e) { + log.error("Problem fetching project from db: " + projectId, e); + } + } + } + + private void updateReleaseByLinkedPackages(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { + String projectId = request.getResourceParameters().getValue(PortalConstants.PROJECT_ID); + if (CommonUtils.isNotNullEmptyOrWhitespace(projectId)) { + final var user = UserCacheHolder.getUserFromRequest(request); + final var projectClient = thriftClients.makeProjectClient(); + Project project; + try { + project = projectClient.getProjectById(projectId, user); + if (CommonUtils.isNotEmpty(project.getPackageIds())) { + final PackageService.Iface packageClient = thriftClients.makePackageClient(); + final var jsonResult = createJSONObject(); + final var packages = packageClient.getPackageByIds(project.getPackageIds()); + Set releaseIdsFromLinkedPackages = CommonUtils.nullToEmptySet( + packages.stream().filter(pkg -> CommonUtils.isNotNullEmptyOrWhitespace(pkg.getReleaseId())) + .map(Package::getReleaseId).collect(Collectors.toSet())); + + final var targetMap = CommonUtils.nullToEmptyMap(project.getReleaseIdToUsage()); + final Set missingReleaseIds = Sets.newHashSet(); + if (CommonUtils.isNotEmpty(releaseIdsFromLinkedPackages)) { + final var releaseRelation = new ProjectReleaseRelationship(ReleaseRelationship.UNKNOWN, MainlineState.OPEN); + for (String releaseId : releaseIdsFromLinkedPackages) { + if (null == targetMap.putIfAbsent(releaseId, releaseRelation)) { + missingReleaseIds.add(releaseId); + } + } + if (CommonUtils.isNotEmpty(missingReleaseIds)) { + projectClient.updateProject(project, user); + } + } + jsonResult.put("data", String.join("||", missingReleaseIds)); + jsonResult.put(SW360Constants.STATUS, SW360Constants.SUCCESS); + try { + writeJSON(request, response, jsonResult); + } catch (IOException e) { + log.error("Problem converting linked packages to JSON! ", e); + } + } + } catch (TException e) { + log.error("Problem fetching project from db: " + projectId, e); + } + } else { + log.error("Project Id cannot be null or empty!"); + } + } + + 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); @@ -3086,6 +3215,7 @@ public JSONArray getProjectData(List projectList, PaginationParameters jsonObject.put("lProjSize", String.valueOf(project.getLinkedProjectsSize())); jsonObject.put("lRelsSize", String.valueOf(project.getReleaseIdToUsageSize())); jsonObject.put("attsSize", String.valueOf(project.getAttachmentsSize())); + jsonObject.put("lPkgSize", String.valueOf(project.getPackageIdsSize())); if (isNotEmpty && groupsWithCrDisabled.contains(project.getBusinessUnit().toLowerCase()) && Objects.isNull(project.getClearingRequestId())) { jsonObject.put("isCrDisabledForProjectBU", true); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortletUtils.java index 55ffc86360..c0bd25efac 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortletUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortletUtils.java @@ -121,6 +121,9 @@ public static void updateProjectFromRequest(PortletRequest request, Project proj case EXTERNAL_URLS: project.setExternalUrls(PortletUtils.getExternalUrlMapFromRequest(request)); break; + case PACKAGE_IDS: + updatePackageIds(request, project); + break; default: setFieldValue(request, project, field); } @@ -131,6 +134,16 @@ public static String getCommentsByTodoId(PortletRequest request, String id) { return request.getParameter("projectobligation:" + id); } + private static void updatePackageIds(PortletRequest request, Project project) { + project.unsetPackageIds(); + String[] ids = request.getParameterValues(PACKAGE_IDS); + if (ids != null && ids.length > 0) { + for (int k = 0; k < ids.length; ++k) { + project.addToPackageIds(ids[k]); + } + } + } + private static void updateLinkedReleasesFromRequest(PortletRequest request, Map releaseUsage) { releaseUsage.clear(); String[] ids = request.getParameterValues(Project._Fields.RELEASE_ID_TO_USAGE.toString() + ReleaseLink._Fields.ID.toString()); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/PackageName.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/PackageName.java new file mode 100644 index 0000000000..8cd8df4d7b --- /dev/null +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/PackageName.java @@ -0,0 +1,27 @@ +/* + * Copyright Siemens Healthineers GmBH, 20235. 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.portal.tags; + +import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.thrift.packages.Package; + +/** + * This tag used used to print the package name and version + * + * @author abdul.kapti@siemens-healthineers.com + */ +public class PackageName extends OutTag { + private static final long serialVersionUID = 1L; + + public void setPkg(Package pkg) { + this.value = SW360Utils.printName(pkg); + } + +} diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLicenseCollection.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLicenseCollection.java index 30d0d41954..88113c45b6 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLicenseCollection.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLicenseCollection.java @@ -31,6 +31,7 @@ import org.apache.commons.lang.StringEscapeUtils; import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.portal.tags.OutTag; public class DisplayLicenseCollection extends TagSupport { private Long scopeGroupId; @@ -40,6 +41,7 @@ public class DisplayLicenseCollection extends TagSupport { private boolean main = true; private String title; private boolean commaJoiner; + private boolean displayLink = true; public void setLicenseIds(Collection licenseIds) { this.licenseIds = licenseIds; @@ -64,6 +66,9 @@ public void setReleaseId(String releaseId) { public String getTitle() { return title; } + public void setDisplayLink(Boolean displayLink) { + this.displayLink = displayLink; + } @Override public int doStartTag() throws JspException { @@ -77,13 +82,21 @@ public int doStartTag() throws JspException { if (CommonUtils.isNullEmptyOrWhitespace(icon)) { for (Iterator iterator = licenseIds.iterator(); iterator.hasNext(); ) { String licenseId = iterator.next(); - DisplayLinkToLicense linkToLicense = new DisplayLinkToLicense(); - linkToLicense.setPageContext(pageContext); - linkToLicense.setScopeGroupId(scopeGroupId); - linkToLicense.setLicenseId(licenseId); + if (displayLink) { + DisplayLinkToLicense linkToLicense = new DisplayLinkToLicense(); + linkToLicense.setPageContext(pageContext); + linkToLicense.setScopeGroupId(scopeGroupId); + linkToLicense.setLicenseId(licenseId); - linkToLicense.doStartTag(); - linkToLicense.doEndTag(); + linkToLicense.doStartTag(); + linkToLicense.doEndTag(); + } else { + OutTag license = new OutTag(); + license.setPageContext(pageContext); + license.setValue(licenseId); + license.doStartTag(); + license.doEndTag(); + } if (iterator.hasNext()) { jspWriter.write(", "); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLinkToSearchResult.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLinkToSearchResult.java index ae1edf4059..ad935ee2d6 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLinkToSearchResult.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/links/DisplayLinkToSearchResult.java @@ -14,7 +14,6 @@ import org.eclipse.sw360.datahandler.thrift.search.SearchResult; import org.eclipse.sw360.portal.common.PortalConstants; import org.eclipse.sw360.portal.common.page.PortletDefaultPage; -import org.eclipse.sw360.portal.common.page.PortletReleasePage; import org.eclipse.sw360.portal.portlets.LinkToPortletConfiguration; import org.eclipse.sw360.portal.tags.urlutils.UrlWriter; @@ -50,19 +49,25 @@ protected void writeUrl() throws JspException { .withParam(PortalConstants.RELEASE_ID, searchResultId); break; case SW360Constants.TYPE_PROJECT: - writer =renderUrl(pageContext) + writer = renderUrl(pageContext) .toPortlet(LinkToPortletConfiguration.PROJECTS, scopeGroupId) .toPage(PortletDefaultPage.DETAIL) .withParam(PortalConstants.PROJECT_ID, searchResultId); break; case SW360Constants.TYPE_COMPONENT: - writer =renderUrl(pageContext) + writer = renderUrl(pageContext) .toPortlet(LinkToPortletConfiguration.COMPONENTS, scopeGroupId) .toPage(PortletDefaultPage.DETAIL) .withParam(PortalConstants.COMPONENT_ID, searchResultId); break; + case SW360Constants.TYPE_PACKAGE: + writer = renderUrl(pageContext) + .toPortlet(LinkToPortletConfiguration.PACKAGES, scopeGroupId) + .toPage(PortletDefaultPage.DETAIL) + .withParam(PortalConstants.PACKAGE_ID, searchResultId); + break; case SW360Constants.TYPE_LICENSE: - writer =renderUrl(pageContext) + writer = renderUrl(pageContext) .toPortlet(LinkToPortletConfiguration.LICENSES, scopeGroupId) .toPage(PortletDefaultPage.DETAIL) .withParam(PortalConstants.LICENSE_ID, searchResultId); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld b/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld index d22d18ad53..f8f89f197b 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld +++ b/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld @@ -802,6 +802,17 @@ true + + PackageName + org.eclipse.sw360.portal.tags.PackageName + empty + + pkg + true + org.eclipse.sw360.datahandler.thrift.packages.Package + true + + DisplayReleaseLink org.eclipse.sw360.portal.tags.links.DisplayLinkToRelease @@ -1043,6 +1054,12 @@ java.lang.Boolean true + + displayLink + false + java.lang.Boolean + true + DisplayComponentLink @@ -1427,6 +1444,12 @@ java.lang.String printName(org.eclipse.sw360.datahandler.thrift.components.Release) + + printPackageName + org.eclipse.sw360.datahandler.common.SW360Utils + java.lang.String printName(org.eclipse.sw360.datahandler.thrift.packages.Package) + + printLicenseName org.eclipse.sw360.datahandler.common.SW360Utils diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss index 14a0732a8e..d32bc8ea95 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss @@ -326,9 +326,6 @@ .clearSelection { cursor: pointer; - height: 96% !important; - background-color: white; - right: 1% !important; pointer-events: auto !important; } } 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/detailRelease.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/detailRelease.jsp index 6177b48f1c..8d52ac7585 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/detailRelease.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/detailRelease.jsp @@ -9,6 +9,7 @@ ~ SPDX-License-Identifier: EPL-2.0 --%> <%@ page import="com.liferay.portal.kernel.portlet.PortletURLFactoryUtil" %> +<%@ page import="org.eclipse.sw360.datahandler.common.SW360Constants" %> <%@ page import="org.eclipse.sw360.portal.common.PortalConstants" %> <%@ page import="javax.portlet.PortletRequest" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.components.ComponentType" %> @@ -47,5 +48,6 @@ + <%@include file="/html/components/includes/releases/detailOverview.jspf"%> diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/editRelease.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/editRelease.jsp index 2e21f33966..bf1738b79e 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/editRelease.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/editRelease.jsp @@ -9,6 +9,7 @@ --%> <%@ page import="com.liferay.portal.kernel.portlet.PortletURLFactoryUtil" %> <%@ page import="javax.portlet.PortletRequest" %> +<%@ page import="org.eclipse.sw360.datahandler.common.SW360Constants" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.attachments.Attachment" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.components.ComponentType" %> <%@ page import="org.eclipse.sw360.datahandler.thrift.components.Release" %> @@ -59,6 +60,7 @@ + <%--These variables are used as a trick to allow referencing enum values in EL expressions below--%> @@ -77,6 +79,9 @@ active" href="#tab-SPDX" data-toggle="list" role="tab"> active" href="#tab-linkedReleases" data-toggle="list" role="tab"> + + active" href="#tab-linkedPackages" data-toggle="list" role="tab"> + active" href="#tab-ClearingDetails" data-toggle="list" role="tab"> active" href="#tab-ECCDetails" data-toggle="list" role="tab"> @@ -102,7 +107,7 @@
@@ -139,8 +144,13 @@ <%@include file="/html/components/includes/releases/editReleaseRepository.jspf" %>
active show" > - <%@include file="/html/utils/includes/editLinkedReleases.jspf" %> +
+ +
active show" > + +
+
active show" > <%@include file="/html/components/includes/releases/editReleaseClearingInformation.jspf" %>
@@ -217,6 +227,9 @@ <%@include file="/html/components/includes/vendors/searchVendor.jspf" %> + + + diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/editPackage.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/editPackage.jsp new file mode 100644 index 0000000000..6ee9acf83c --- /dev/null +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/editPackage.jsp @@ -0,0 +1,307 @@ +<%-- + ~ 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 + --%> +<%@ page import="org.eclipse.sw360.datahandler.common.SW360Utils" %> +<%@ page import="org.eclipse.sw360.datahandler.thrift.packages.Package" %> +<%@ page import="org.eclipse.sw360.datahandler.thrift.packages.PackageManagerType" %> +<%@ page import="javax.portlet.PortletRequest" %> +<%@ page import="com.liferay.portal.kernel.portlet.PortletURLFactoryUtil" %> +<%@ page import="org.eclipse.sw360.portal.common.PortalConstants" %> + +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +<%@ include file="/html/init.jsp" %> +<%-- the following is needed by liferay to display error messages--%> +<%@ include file="/html/utils/includes/errorKeyToMessage.jspf"%> +<%-- use require js on this page --%> +<%@include file="/html/utils/includes/requirejs.jspf" %> + + + + + + + + + + + + + + + + + + + + +<%@include file="/html/utils/includes/logError.jspf" %> + + + + + + + +
+ +
+
+ + diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/summaryPackage.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/summaryPackage.jspf new file mode 100644 index 0000000000..fa620041f6 --- /dev/null +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/summaryPackage.jspf @@ -0,0 +1,95 @@ +<%-- + ~ 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 +--%> +<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
: + +
:
:
:
:
:
:
:
:
: + + + + + + "> + + + +
:
:
:
:
+ +<%--for javascript library loading --%> +<%@ include file="/html/utils/includes/requirejs.jspf" %> +
diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/view.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/view.jsp new file mode 100644 index 0000000000..1820f913f0 --- /dev/null +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/packages/view.jsp @@ -0,0 +1,391 @@ +<%-- + ~ 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 + --%> + +<%@include file="/html/init.jsp" %> +<%-- the following is needed by liferay to display error messages--%> +<%@ include file="/html/utils/includes/errorKeyToMessage.jspf"%> + +<%@ page import="com.liferay.portal.kernel.portlet.PortletURLFactoryUtil" %> +<%@ page import="javax.portlet.PortletRequest" %> +<%@ page import="org.eclipse.sw360.datahandler.thrift.DateRange" %> +<%@ page import="org.eclipse.sw360.datahandler.thrift.packages.Package" %> +<%@ page import="org.eclipse.sw360.datahandler.thrift.packages.PackageManagerType" %> +<%@ page import="org.eclipse.sw360.portal.common.PortalConstants" %> + +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<%@ include file="/html/utils/includes/pageSpinner.jspf" %> + +
+<%--for javascript library loading --%> +<%@ include file="/html/utils/includes/requirejs.jspf" %> + \ No newline at end of file diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/edit.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/edit.jsp index 89edec7e18..c29c2ac245 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/edit.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/edit.jsp @@ -27,6 +27,7 @@ <%-- the following is needed by liferay to display error messages--%> <%@ include file="/html/utils/includes/errorKeyToMessage.jspf"%> + @@ -87,6 +88,9 @@ active" href="#tab-Summary" data-toggle="list" role="tab"> active" href="#tab-Administration" data-toggle="list" role="tab"> active" href="#tab-linkedProjects" data-toggle="list" role="tab"> + + active" href="#tab-linkedPackages" data-toggle="list" role="tab"> + active" href="#tab-Attachments" data-toggle="list" role="tab"> @@ -144,6 +148,7 @@ data-comment-parameter-name="<%=PortalConstants.MODERATION_REQUEST_COMMENT%>" data-linked-projects="${project.linkedProjectsSize}" data-linked-releases="${project.releaseIdToUsageSize}" + data-linked-packages="${project.packageIdsSize}" data-attachments="${project.attachmentsSize}" >
@@ -168,6 +173,11 @@ <%@include file="/html/projects/includes/linkedProjectsEdit.jspf" %> <%@include file="/html/utils/includes/linkedReleasesEdit.jspf" %>
+ +
active show"> + +
+
active show"> <%@include file="/html/utils/includes/editAttachments.jspf" %> @@ -209,6 +219,7 @@
  • +
@@ -233,6 +244,9 @@ + + + <%@include file="/html/components/includes/vendors/searchVendor.jspf" %> @@ -350,6 +364,7 @@ require(['jquery', 'modules/autocomplete', 'modules/dialog', 'modules/listgroup' data = $('#projectEditForm').data(), linkedProjectsSize = data.linkedProjects, linkedReleasesSize = data.linkedReleases, + linkedPackagesSize = data.linkedPackages, attachmentsSize = data.attachments; function deleteProjectInternal() { @@ -362,10 +377,12 @@ require(['jquery', 'modules/autocomplete', 'modules/dialog', 'modules/listgroup' name: name, linkedProjects: linkedProjectsSize, linkedReleases: linkedReleasesSize, + linkedPackages: linkedPackagesSize, attachments: attachmentsSize, - hasNoDependencies: linkedProjectsSize == 0 && linkedReleasesSize == 0 && attachmentsSize == 0, + hasNoDependencies: linkedProjectsSize == 0 && linkedReleasesSize == 0 && attachmentsSize == 0 && linkedPackagesSize == 0, hasNoLinkedProjects: linkedProjectsSize == 0, hasNoLinkedReleases: linkedReleasesSize == 0, + hasNoLinkedPackages: linkedPackagesSize == 0, hasNoAttachments: attachmentsSize == 0 }, function(submit, callback) { deleteProjectInternal(); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/includes/detailOverview.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/includes/detailOverview.jspf index 7dd02adcf6..a188bd16a5 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/includes/detailOverview.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/projects/includes/detailOverview.jspf @@ -28,6 +28,7 @@ +