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

com.metaeffekt.artifact.analysis.bom.cyclonedx.ModelBaseMapper Maven / Gradle / Ivy

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

import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import com.metaeffekt.artifact.analysis.bom.BomConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.cyclonedx.model.*;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.AssetMetaData;
import org.metaeffekt.core.inventory.processor.model.Constants;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Counterpart to the {@link ComponentMapper} responsible for mapping component back to either an artifact or aseet.
 */
@Slf4j
public class ModelBaseMapper {

    /**
     * Main entrypoint when mapping a component.
     *
     * @param component the component to map.
     * @return an AbstractModelBase object, either an instance of asset or artifact.
     */
    public AbstractModelBase map(Component component, boolean includeMetadataComponent) {
        AbstractModelBase modelBase;

        if (isAsset(component)) {
            modelBase = new AssetMetaData();
        } else {
            modelBase = new Artifact();
        }

        mapOrganizationInformation(component, modelBase);
        mapBaseInformation(component, modelBase);
        mapPurlRelated(component, modelBase);
        mapChecksums(component, modelBase);
        mapFileLocationInformation(component, modelBase);
        mapLicenses(component, modelBase);

        if (includeMetadataComponent) {
            List properties = component.getProperties().stream().filter(p -> !p.getName().equals(BomConstants.INVENTORY_CLASS)).collect(Collectors.toList());
            Property property = new Property();
            property.setName(BomConstants.INVENTORY_CLASS);
            property.setValue("artifact");
            properties.add(property);
            component.setProperties(properties);
            map(component, false);
        }

        return modelBase;
    }

    /**
     * Maps basic information which should be contained in either all or most components, depending on the stage during
     * which the bom was generated.
     *
     * @param component the component
     * @param modelBase the artifact or asset
     */
    private void mapBaseInformation(Component component, AbstractModelBase modelBase) {
        if (isAsset(component)) {
            setIfNotBlank(modelBase, Constants.KEY_ASSET_ID, component.getName());
        } else {
            setIfNotBlank(modelBase, Constants.KEY_ID, component.getName());
        }
        setIfNotBlank(modelBase, Constants.KEY_VERSION, component.getVersion());
        setIfNotBlank(modelBase, Constants.KEY_DESCRIPTION, component.getDescription());

        List properties = component.getProperties();
        if (properties != null) {
            Property property =
                    properties.stream().filter(p -> p.getName().equals(BomConstants.SPECIFIC_TYPE)).findFirst()
                            .orElse(null);
            if (property != null) {
                setIfNotBlank(modelBase, Constants.KEY_TYPE, property.getValue());
            }

            property = properties.stream().filter(p -> p.getName().equals(Artifact.Attribute.COMMENT.getKey())).findFirst().orElse(null);
            if (property != null) {
                setIfNotBlank(modelBase, Artifact.Attribute.COMMENT.getKey(), property.getValue());
            }
        }
    }

    private void mapPurlRelated(Component component, AbstractModelBase modelBase) {
        final String purl = component.getPurl();
        if (purl != null) {
            setIfNotBlank(modelBase, Constants.KEY_PURL, purl);
            try {
                PackageURL packageURL = new PackageURL(purl);
                setIfNotBlank(modelBase, Artifact.Attribute.COMPONENT.getKey(), packageURL.getName());
            } catch (MalformedPackageURLException e) {
                throw new RuntimeException(e);
            }
        }

    }

    /**
     * Maps checksums and hashes.
     *
     * @param component the component
     * @param modelBase the artifact or asset
     */
    private void mapChecksums(Component component, AbstractModelBase modelBase) {

        if (component.getHashes() == null || component.getHashes().isEmpty()) {
            return;
        }

        for (Hash hash : component.getHashes()) {
            if (hash.getAlgorithm().equals(Hash.Algorithm.MD5.getSpec())) {
                modelBase.set(Constants.KEY_CHECKSUM, hash.getValue());
            } else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA1.getSpec())) {
                modelBase.set(Constants.KEY_HASH_SHA1, hash.getValue());
            } else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA_256.getSpec())) {
                modelBase.set(Constants.KEY_HASH_SHA256, hash.getValue());
            } else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA_512.getSpec())) {
                modelBase.set(Constants.KEY_HASH_SHA512, hash.getValue());
            }
        }
    }

    /**
     * Maps the file location information.
     *
     * @param component the component
     * @param modelBase the asset or artifact
     */
    private void mapFileLocationInformation(Component component, AbstractModelBase modelBase) {
        setIfPropertyExists(modelBase, Constants.KEY_PROJECTS, "Path", component);
    }

    /**
     * Maps all license to the "Component Specified License" field. As license handling is quite complicated and
     * translating all licenses back to their original state before the bom was created is not possible, this is
     * done to
     * keep the process as simple as possible.
     *
     * @param component the component
     * @param modelBase the asset or artifact
     */
    private void mapLicenses(Component component, AbstractModelBase modelBase) {
        String licenseString = null;
        final LicenseChoice licenseChoice = component.getLicenses();
        if (licenseChoice != null) {
            if (licenseChoice.getExpression() != null) {
                licenseString = licenseChoice.getExpression().getValue();
            }
            final List licenseList = licenseChoice.getLicenses();
            if (licenseList != null) {
                licenseString = licenseList.stream().map(this::licenseToName).collect(Collectors.joining(", "));
            }
        }

        setIfNotBlank(modelBase, Constants.KEY_COMPONENT_SPECIFIED_LICENSE, licenseString);
    }

    /**
     * Maps organization information.
     *
     * @param component the component
     * @param modelBase the artifact or asset
     */
    private void mapOrganizationInformation(Component component, AbstractModelBase modelBase) {
        setIfNotBlank(modelBase, Constants.KEY_URL, component.getPublisher());
        setIfNotBlank(modelBase, Constants.KEY_GROUP_ID, component.getGroup());
        setIfPropertyExists(modelBase, Constants.KEY_ORGANIZATION_URL, Constants.KEY_ORGANIZATION_URL, component);
        setIfPropertyExists(modelBase, Constants.KEY_ORGANIZATION, Constants.KEY_ORGANIZATION, component);
    }

    /**
     * Tries to determine if a component was an artifact or asset. This only works reliably if includeTechnicalProperties
     * in {@link com.metaeffekt.artifact.analysis.bom.spdx.DocumentSpec} was set to true during the export from the original inventory.
     *
     * @param component the component
     * @return true if the component used to be an asset.
     */
    private boolean isAsset(Component component) {

        try {
            return component.getProperties().stream()
                    .anyMatch(p -> p.getName().equals(BomConstants.INVENTORY_CLASS) && p.getValue().equals("asset"));
        } catch (NullPointerException e) {
            // Fallback if required property does not exist.
            return component.getName().startsWith("CID") || component.getName().startsWith("AID");
        }
    }

    private void setIfNotBlank(AbstractModelBase modelBase, String key, String value) {
        if (StringUtils.isNotBlank(value)) {
            modelBase.set(key, value);
        }
    }

    /**
     * Looks up a property by the name associated with it and adds the value to the respective Attribute of the given
     * AbstractModelBase object.
     *
     * @param modelBase the artifact or asset
     * @param key       the attribute key of the artifact/asset for which to add the value
     * @param name      the name of the property
     * @param component contains the list of properties
     */
    private void setIfPropertyExists(AbstractModelBase modelBase, String key, String name, Component component) {
        List properties = component.getProperties();
        if (properties != null) {
            Optional optionalProperty =
                    properties.stream().filter(p -> p.getName().equals(name)).findFirst();
            if (optionalProperty.isPresent()) {
                Property property = optionalProperty.get();
                setIfNotBlank(modelBase, key, property.getValue());
            }
        }
    }

    private String licenseToName(License l) {
        if (l.getId() != null) {
            return l.getId();
        }
        return l.getName();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy