All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.bom.LicenseProcessor Maven / Gradle / Ivy

The newest version!
package com.metaeffekt.artifact.analysis.bom;

import com.metaeffekt.artifact.analysis.metascan.Constants;
import com.metaeffekt.artifact.analysis.bom.spdx.LicenseStringConverter;
import com.metaeffekt.artifact.analysis.utils.InventoryUtils;
import com.metaeffekt.artifact.terms.model.LicenseTextEntry;
import com.metaeffekt.artifact.terms.model.LicenseTextProvider;
import com.metaeffekt.artifact.terms.model.NormalizationMetaData;
import com.metaeffekt.artifact.terms.model.TermsMetaData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.metaeffekt.common.notice.model.ComponentDefinition;
import org.metaeffekt.common.notice.model.NoticeParameters;
import org.metaeffekt.common.notice.model.UnassignedInformation;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.Artifact;

import java.util.*;
import java.util.stream.Collectors;

/**
 * A helper class used in conjunction with CycloneDX and SPDX to perform various actions designed to help find, filter
 * and manage licenses and license expressions.
 */
@Slf4j
public class LicenseProcessor {

    public static Map getReferencedLicenseText(Collection referencedLicenses,
               LicenseTextProvider licenseTextProvider, NormalizationMetaData normalizationMetaData) {

        if (referencedLicenses.isEmpty()) {
            return null;
        }

        final Map identifierToLicenseText = new HashMap<>();

        final Set referencedCanonicalNames = referencedLicenses.stream()
                .map(TermsMetaData::getCanonicalName).collect(Collectors.toSet());

        final LicenseStringConverter licenseStringConverter = new LicenseStringConverter(normalizationMetaData, null);
        final Map licenseTextEntryByName;

        if (licenseTextProvider != null) {
            licenseTextEntryByName = licenseTextProvider.resolve(referencedCanonicalNames);
        } else {
            licenseTextEntryByName = Collections.emptyMap();
        }

        // process licenses for which texts have been found
        for (Map.Entry licenseNameToTextEntry : licenseTextEntryByName.entrySet()) {
            final String canonicalName = licenseNameToTextEntry.getKey();

            TermsMetaData tmd = normalizationMetaData.findTermsMetaData(canonicalName);
            if (tmd == null) {
                tmd = normalizationMetaData.findUsingCanonicalNameInHistory(canonicalName);
                if (tmd == null) continue;
            }
            final String identifier = licenseStringConverter.getLicenseRefForTmd(tmd);
            identifierToLicenseText.put(identifier, licenseNameToTextEntry.getValue().getLicenseText());
        }

        // add null for licenses without texts
        referencedCanonicalNames.removeAll(licenseTextEntryByName.keySet());
        for (String canonicalName : referencedCanonicalNames) {
            TermsMetaData tmd = normalizationMetaData.findTermsMetaData(canonicalName);
            if (tmd == null) {
                tmd = normalizationMetaData.findUsingCanonicalNameInHistory(canonicalName);
                if (tmd == null) continue;
            }
            final String identifier = licenseStringConverter.getLicenseRefForTmd(tmd);
            identifierToLicenseText.put(identifier, null);
        }

        return identifierToLicenseText;
    }

    public static List aggregateEffectiveLicenses(NoticeParameters noticeParameters) {
        final Set aggregated = new HashSet<>();
        if (noticeParameters.getComponent() != null) {
            aggregated.addAll(aggregateEffectiveLicenses(noticeParameters.getComponent()));
        }

        if (noticeParameters.getSubcomponents() != null) {
            for (ComponentDefinition componentDefinition : noticeParameters.getSubcomponents()) {
                aggregated.addAll(aggregateEffectiveLicenses(componentDefinition));
            }
        }
        return aggregated.stream().sorted(String::compareToIgnoreCase).collect(Collectors.toList());
    }

    private static Collection aggregateEffectiveLicenses(ComponentDefinition component) {
        if (component != null) {
            if (component.getEffectiveLicenses() == null || component.getEffectiveLicenses().isEmpty()) {
                final List associatedLicenses = component.getAssociatedLicenses();
                return InventoryUtils.tokenizeLicense(InventoryUtils.deriveEffectiveLicenses(associatedLicenses), true,
                        false);
            } else {
                return component.getEffectiveLicenses();
            }
        }
        return Collections.emptyList();
    }

    public static NoticeParameters readNoticeParameters(AbstractModelBase modelBase) {
        // get notice parameter from the artifact
        String noticeParametersString = modelBase.get(Constants.KEY_NOTICE_PARAMETER);

        // FIXME-KKL: resolve issue
        // the inherited notice parameter is curated; (could be also an exact match; since no inventory filtering is
        // applied yet)
        if (StringUtils.isBlank(noticeParametersString)) {
            noticeParametersString = modelBase.get("Inherited Notice Parameter");
        }

        // the derived notice parameter is automatically determined
        if (StringUtils.isBlank(noticeParametersString)) {
            noticeParametersString = modelBase.get(Constants.KEY_DERIVED_NOTICE_PARAMETER);
        }

        if (StringUtils.isNotBlank(noticeParametersString)) {
            // convert notice parameter string into object
            try {
                return NoticeParameters.readYaml(noticeParametersString);
            } catch (RuntimeException e) {
                log.warn("Unable to parse notice parameter:\n[{}]", noticeParametersString);
            }
        }
        String associatedLicenses = null;
        List copyrights = null;
        // fallback to other provided licenses/copyrights
        if (modelBase instanceof Artifact) {
            associatedLicenses = deriveAssociatedLicenses((Artifact) modelBase);
            copyrights = extractCopyrights((Artifact) modelBase);
        }

        if (StringUtils.isNotEmpty(associatedLicenses)) {
            final ComponentDefinition componentDefinition = new ComponentDefinition();
            componentDefinition.setAssociatedLicenses(InventoryUtils.tokenizeLicense(associatedLicenses, true, true));
            final NoticeParameters noticeParameters = new NoticeParameters();
            noticeParameters.setComponent(componentDefinition);
            UnassignedInformation unassignedInformation = new UnassignedInformation();
            unassignedInformation.setCopyrights(copyrights);
            noticeParameters.setUnassignedInformation(unassignedInformation);
            return noticeParameters;
        } else if (copyrights != null && !copyrights.isEmpty()) {
            final NoticeParameters noticeParameters = new NoticeParameters();
            UnassignedInformation unassignedInformation = new UnassignedInformation();
            unassignedInformation.setCopyrights(copyrights);
            noticeParameters.setUnassignedInformation(unassignedInformation);
            return noticeParameters;
        }

        // unable to proceed with empty licenses / notice parameter
        return null;
    }

    private static String deriveAssociatedLicenses(Artifact artifact) {
        // getLicenses delivers the curated associated licenses
        String associatedLicenses = artifact.getLicense();

        // FIXME: currently a fallback strategy
        // FIXME: merge binary and source license lists (via set union) to create the most complete possible list
        // FIXME: consider approach via notice parameter for each level and merging the component pattern in the end

        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get("Binary Artifact - Derived Licenses");
        }

        // fallback to detected licenses on binary
        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get(Constants.KEY_DERIVED_LICENSES);
        }

        // fallback to source artifact
        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get("Source Artifact - Derived Licenses");
        }

        // fallback on source archive
        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get("Source Archive - Derived Licenses");
        }

        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get("Descriptor - Derived Licenses");
        }

        // fallback to package specified licenses (mapped)
        if (StringUtils.isEmpty(associatedLicenses)) {
            associatedLicenses = artifact.get("Specified Package License (mapped)");
        }

        return associatedLicenses;
    }

    /**
     * Attempts to extract copyright information from the artifact or returns "NOASSERTION".
     *
     * @return text containing copyright information or "NOASSERTION"
     */
    private static List extractCopyrights(Artifact artifact) {
        String copyrights = artifact.get(Constants.KEY_EXTRACTED_COPYRIGHTS_SCANCODE);

        // FIXME: currently a fallback strategy
        if (StringUtils.isEmpty(copyrights)) {
            copyrights = artifact.get("Binary Artifact - Extracted Copyrights (ScanCode)");
        }

        if (StringUtils.isEmpty(copyrights)) {
            copyrights = artifact.get("Source Artifact - Extracted Copyrights (ScanCode)");
        }

        if (StringUtils.isEmpty(copyrights)) {
            copyrights = artifact.get("Source Archive - Extracted Copyrights (ScanCode)");
        }

        if (StringUtils.isEmpty(copyrights)) {
            copyrights = artifact.get("Descriptor - Extracted Copyrights (ScanCode)");
        }

        if (!StringUtils.isEmpty(copyrights)) {
            return InventoryUtils.filterCopyrightsAndAuthorsList(copyrights);
        }

        return Collections.emptyList();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy