com.metaeffekt.artifact.analysis.spdxbom.mapper.PackageFillers Maven / Gradle / Ivy
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.artifact.analysis.spdxbom.mapper;
import com.metaeffekt.artifact.analysis.metascan.Constants;
import com.metaeffekt.artifact.analysis.spdxbom.context.SpdxDocumentContext;
import com.metaeffekt.artifact.analysis.spdxbom.holder.FillPackageHolder;
import com.metaeffekt.artifact.analysis.spdxbom.mapper.exception.PurlGenerationFailedException;
import org.apache.commons.lang3.StringUtils;
import org.metaeffekt.common.notice.model.ComponentDefinition;
import org.metaeffekt.common.notice.model.NoticeParameters;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spdx.library.InvalidSPDXAnalysisException;
import org.spdx.library.SpdxConstants;
import org.spdx.library.model.Checksum;
import org.spdx.library.model.ExternalRef;
import org.spdx.library.model.ReferenceType;
import org.spdx.library.model.SpdxDocument;
import org.spdx.library.model.enumerations.AnnotationType;
import org.spdx.library.model.enumerations.ChecksumAlgorithm;
import org.spdx.library.model.enumerations.ReferenceCategory;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiFunction;
import static com.metaeffekt.artifact.analysis.metascan.Constants.KEY_BINARY_ARTIFACT_HASH_SHA256;
import static com.metaeffekt.artifact.analysis.spdxbom.mapper.MappingUtils.getOverflowAnnotation;
import static org.metaeffekt.core.inventory.processor.model.Constants.KEY_HASH_SHA1;
import static org.metaeffekt.core.inventory.processor.model.Constants.KEY_HASH_SHA256;
/**
* Contains static filler helpers for some of the default package fields.
* This helps avoid code duplication in the individual mapper classes.
*/
public class PackageFillers {
private static final Logger LOG = LoggerFactory.getLogger(PackageFillers.class);
public static void fillComment(FillPackageHolder fillHolder) {
fillHolder.getPackageBuilder().setComment(fillHolder.getArtifact().getComment());
fillHolder.getAttributesWritten().add(Artifact.Attribute.COMMENT.getKey());
}
public static void fillHomepageFromUrl(FillPackageHolder fillHolder) {
final String url = fillHolder.getArtifact().getUrl();
if (StringUtils.isNotEmpty(url)) {
try {
// FIXME: beautify
new URL(url);
fillHolder.getPackageBuilder().setHomepage(url);
fillHolder.getAttributesWritten().add(Artifact.Attribute.URL.getKey());
} catch (MalformedURLException e) {
LOG.warn("URL value is not a valid URL: [{}]", url);
}
}
}
public static void fillVersionInfo(FillPackageHolder fillHolder) {
if (fillHolder.getArtifact().getVersion() == null) {
return;
}
fillHolder.getPackageBuilder().setVersionInfo(fillHolder.getArtifact().getVersion());
fillHolder.getAttributesWritten().add(Artifact.Attribute.VERSION.getKey());
}
public static void fillChecksums(FillPackageHolder fillHolder, SpdxDocument document) throws InvalidSPDXAnalysisException {
final Artifact artifact = fillHolder.getArtifact();
List checksums = new ArrayList<>();
// FIXME: consolidate names
String checksum = artifact.getChecksum();
if (StringUtils.isBlank(checksum)) {
checksum = artifact.get("Checksum (MD5)");
}
if (StringUtils.isNotBlank(checksum)) {
checksums.add(document.createChecksum(ChecksumAlgorithm.MD5, checksum));
fillHolder.getAttributesWritten().add(Artifact.Attribute.CHECKSUM.getKey());
fillHolder.getAttributesWritten().add("Checksum (MD5)");
}
// FIXME: consolidate names
String sha1 = artifact.get("Checksum (SHA-1)");
if (StringUtils.isBlank(sha1)) {
sha1 = artifact.get(KEY_HASH_SHA1);
}
if (StringUtils.isNotBlank(sha1)) {
checksums.add(document.createChecksum(ChecksumAlgorithm.SHA1, sha1));
fillHolder.getAttributesWritten().add("Checksum (SHA-1)");
fillHolder.getAttributesWritten().add(KEY_HASH_SHA1);
}
// FIXME: consolidate names
String sha256 = artifact.get("Checksum (SHA-256)");
if (StringUtils.isBlank(sha256)) {
sha256 = artifact.get("Hash (SHA-256)");
}
if (StringUtils.isBlank(sha256)) {
// the observed checksum is not available
sha256 = artifact.get(KEY_BINARY_ARTIFACT_HASH_SHA256);
}
if (StringUtils.isNotBlank(sha256)) {
checksums.add(document.createChecksum(ChecksumAlgorithm.SHA256, sha256));
fillHolder.getAttributesWritten().add("Checksum (SHA-256)");
fillHolder.getAttributesWritten().add(KEY_HASH_SHA256);
fillHolder.getAttributesWritten().add(KEY_BINARY_ARTIFACT_HASH_SHA256);
}
if (!checksums.isEmpty()) {
fillHolder.getPackageBuilder().setChecksums(checksums);
}
}
public static void fillRequired(FillPackageHolder fillHolder) {
// set filesAnalyzed to false. at the time of writing, spdx file output would require additional parsing.
// TODO: consider outputting file-by-file outputs?
fillHolder.getPackageBuilder().setFilesAnalyzed(false);
fillHolder.getPackageBuilder().setDownloadLocation(SpdxConstants.NOASSERTION_VALUE);
}
public static void fillAuthors(FillPackageHolder fillHolder) {
String authorsKey = "Extracted Authors (ScanCode-1)";
String authors = fillHolder.getArtifact().get(authorsKey);
if (StringUtils.isNotBlank(authors)) {
fillHolder.getPackageBuilder().setAttributionText(Arrays.asList(authors.split("\\|\n")));
}
fillHolder.getAttributesWritten().add(authorsKey);
// FIXME: fileContributors cannot be set via api (or was not found); use instead of setAttributionText(...)
// what was meant by this? perhaps per-file outputs were confused with package fields?
}
public static void fillOverflowKeyValueAnnotation(FillPackageHolder fillHolder,
SpdxDocumentContext documentContext,
Collection approved)
throws InvalidSPDXAnalysisException {
Map validOverflowContent = new HashMap<>();
// iterate keys that were approved for inclusion by key-value map
for (String key : approved) {
if (fillHolder.getAttributesWritten().contains(key)) {
// we already wrote this (possibly properly by mapper). do not output again
continue;
}
// get the value for this particular attribute
String value = fillHolder.getArtifact().get(key);
// only add the attribute if the value is not empty
if (StringUtils.isNotBlank(value)) {
validOverflowContent.put(key, value);
}
// mark this attribute as "written"
fillHolder.getAttributesWritten().add(key);
}
if (!validOverflowContent.isEmpty()) {
fillHolder.getPackageBuilder().addAnnotation(getOverflowAnnotation(documentContext, validOverflowContent));
}
}
/**
* Fills an external ref with a purl generated by purlGetter.
* @param fillHolder helper object with required objects for fillers
* @param document the document that this package is created from and added to
* @param purlGetter a method parameter for generating the purl string from the artifact
* @param referenceCategory a reference category for the "external reference" as defined by spdx
*
* @throws InvalidSPDXAnalysisException Throws InvalidSPDXAnalysisException.
*/
public static void fillExternalRefWithPurl(FillPackageHolder fillHolder, SpdxDocument document,
BiFunction, String> purlGetter,
ReferenceCategory referenceCategory) throws InvalidSPDXAnalysisException {
final Artifact artifact = fillHolder.getArtifact();
try {
// prepare purl to hold artifact information
final String purlString = purlGetter.apply(artifact, fillHolder.getAttributesWritten());
final ReferenceType referenceType = new ReferenceType(
new ReferenceType(SpdxConstants.SPDX_LISTED_REFERENCE_TYPES_PREFIX + "purl")
);
final ExternalRef externalRef = document.createExternalRef(referenceCategory, referenceType, purlString, null);
fillHolder.getPackageBuilder().addExternalRef(externalRef);
} catch (PurlGenerationFailedException e) {
LOG.warn("Purl generation failed for artifact [{}]. Leaving external reference empty.", artifact);
}
}
/**
* This method gets what we call "notes" from notice parameters and sets them.
* We may want to merge this with {@link PackageFillers#fillAuthors(FillPackageHolder)}, since the two are similar in nature.
* Spdx also doesn't fully specify what's supposed to be in the attribution text field.
* I think the notes might be a better fit than whatever authors field we had so far, but both qualify as correct.
*
* Until we discuss changes, I'll be filling this information into an annotation so it's at least present SOMEWHERE.
* @param fillHolder helper object with commonly required data for filling
* @param context context required to build annotations using the document
* @param noticeParam The {@link NoticeParameters} instance to use if available.
*
* @throws InvalidSPDXAnalysisException Thrown in case SPDX library subroutines fail.
*/
public static void fillNotesAnnotation(FillPackageHolder fillHolder,
SpdxDocumentContext context,
NoticeParameters noticeParam) throws InvalidSPDXAnalysisException {
if (noticeParam == null) {
// no need to get data that isn't there.
return;
}
String notesAnnotationText = buildNotesAnnotation(noticeParam);
if (StringUtils.isNotBlank(notesAnnotationText)) {
fillHolder.getPackageBuilder().addAnnotation(
context.getSpdxDocument().createAnnotation(
"Tool: " + context.getSpdxDocumentSpec().getTool(),
// storing additional data, so type is OTHER
AnnotationType.OTHER,
new SimpleDateFormat(SpdxConstants.SPDX_DATE_FORMAT).format(new Date()),
notesAnnotationText
)
);
}
}
private static String buildNotesAnnotation(NoticeParameters noticeParam) {
StringJoiner annotationTextsJoiner = new StringJoiner("\n\n");
if (noticeParam.getComponent() != null && noticeParam.getComponent().getNote() != null ) {
annotationTextsJoiner.add(
"The component \"" + noticeParam.getComponent().getName() + "\" has notes:\n" +
noticeParam.getComponent().getNote()
);
}
if (noticeParam.getSubcomponents() != null) {
for (ComponentDefinition subcomp : noticeParam.getSubcomponents()) {
if (subcomp.getNote() != null) {
annotationTextsJoiner.add(
"The subcomponent \"" + subcomp.getName() + "\" has notes:\n" +
subcomp.getNote()
);
}
}
}
return annotationTextsJoiner.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy