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

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

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

import com.metaeffekt.artifact.analysis.bom.BomConstants;
import org.cyclonedx.exception.ParseException;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Metadata;
import org.cyclonedx.model.Property;
import org.cyclonedx.parsers.BomParserFactory;
import org.cyclonedx.parsers.Parser;
import org.metaeffekt.core.inventory.processor.model.*;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Stack;

/**
 * This class is responsible for importing bom's or json files containing bom's to an inventory. The class is to be
 * mainly used in conjunction with the {@link CycloneDxExporter} but should work with any other cyclonedx-bom
 * provided, though fields tracked through the use of {@link org.cyclonedx.model.Property} will not be imported or
 * might be imported incorrectly.
 */
public class CycloneDxImporter {

    // Flag, when true the metadata-component is listed in the artifacts sheet in addition to the assets sheet.
    boolean includeMetadataComponent;

    public CycloneDxImporter(boolean includeMetadataComponent) {
        this.includeMetadataComponent = includeMetadataComponent;
    }

    /**
     * Main entrypoint when importing from json file containing a bom.
     *
     * @param bomFile the json file containing a bom
     * @return the imported inventory.
     */
    public Inventory importFromFile(File bomFile) {
        try {
            final Parser parser = BomParserFactory.createParser(bomFile);
            final Bom parsedBom = parser.parse(bomFile);
            return importFromBom(parsedBom);
        } catch (ParseException e) {
            throw new RuntimeException("CycloneDX built-in parser failed to parse file.", e);
        }
    }

    /**
     * Main entrypoint when importing from a {@link Bom} object directly.
     *
     * @param bom the bom
     * @return the imported inventory.
     */
    public Inventory importFromBom(Bom bom) {
        Inventory inventory = new Inventory();
        List artifacts = new ArrayList<>();
        List assets = new ArrayList<>();

        importAssetsAndArtifacts(bom, artifacts, assets);
        importHierarchy(bom, artifacts, assets);

        inventory.setArtifacts(artifacts);
        inventory.setAssetMetaData(assets);

        return inventory;
    }

    /**
     * Collects all components in the bom and runs them through the {@link ModelBaseMapper} which maps all fields and
     * values to their respective counterpart in our inventory. Iterative approach used to avoid issues with deeply
     * nested component hierarchies.
     *
     * @param bom       the bom
     * @param artifacts a list of artifacts
     * @param assets    a list of assets
     */
    private void importAssetsAndArtifacts(Bom bom, List artifacts, List assets) {
        List allComponents = new ArrayList<>();
        Stack componentStack = new Stack<>();
        ModelBaseMapper modelBaseMapper = new ModelBaseMapper();

        for (Component component : bom.getComponents()) {
            componentStack.push(component);
        }

        // Metadata component mapping preparations
        Metadata metadata = bom.getMetadata();
        Component metadataComponent = null;
        if (metadata != null) {
            metadataComponent = bom.getMetadata().getComponent();
        }
        if (metadataComponent != null) {
            Property property = new Property();
            property.setName(BomConstants.INVENTORY_CLASS);
            property.setValue("assets");
            Property propertyPrimary = new Property();
            propertyPrimary.setName(Constants.KEY_PRIMARY);
            propertyPrimary.setValue("yes");
            metadataComponent.addProperty(property);
            metadataComponent.addProperty(propertyPrimary);
            AbstractModelBase modelBase = modelBaseMapper.map(metadataComponent, true);
            if (modelBase instanceof Artifact) {
                artifacts.add((Artifact) modelBase);
            } else {
                assets.add((AssetMetaData) modelBase);
            }
        }

        while (!componentStack.isEmpty()) {
            Component currentComponent = componentStack.pop();
            allComponents.add(currentComponent);
            if (currentComponent.getComponents() != null) {
                for (Component childComponent : currentComponent.getComponents()) {
                    componentStack.push(childComponent);
                }
            }
        }

        for (Component component : allComponents) {
            AbstractModelBase modelBase = modelBaseMapper.map(component, false);
            if (modelBase instanceof Artifact) {
                artifacts.add((Artifact) modelBase);
            } else {
                assets.add((AssetMetaData) modelBase);
            }
        }
    }

    /**
     * Imports the relationship hierarchy contained in the bom. Currently capable of mapping DESCRIBES and CONTAINS
     * relationships.
     *
     * @param bom       the bom
     * @param artifacts list of artifacts
     * @param assets    list of assets
     */
    private void importHierarchy(Bom bom, List artifacts, List assets) {
        Stack componentStack = new Stack<>();

        for (Component component : bom.getComponents()) {
            componentStack.push(component);
        }

        while (!componentStack.isEmpty()) {
            Component currentComponent = componentStack.pop();
            if (currentComponent.getComponents() == null) {
                continue;
            }
            for (Component childComponent : currentComponent.getComponents()) {
                Optional artifact =
                        artifacts.stream().filter(a -> a.get(Constants.KEY_ID).equals(childComponent.getName()))
                                .findFirst();
                Optional asset =
                        assets.stream().filter(a -> a.get(Constants.KEY_ASSET_ID).equals(childComponent.getName()))
                                .findFirst();
                artifact.ifPresent(a -> a.set(currentComponent.getName(), Constants.MARKER_CONTAINS));
                asset.ifPresent(a -> a.set(currentComponent.getName(), Constants.MARKER_CONTAINS));
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy