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

com.metaeffekt.artifact.analysis.utils.InventoryDiffMerger Maven / Gradle / Ivy

The newest version!
/*
 * 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.utils;

import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;

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

public class InventoryDiffMerger {

    private final Inventory referenceInventory, specificInventory;
    private Inventory mergedInventory;
    private Map compareAttributes = new HashMap<>();

    public InventoryDiffMerger(Inventory referenceInventory, Inventory specificInventory) {
        this.referenceInventory = referenceInventory;
        this.specificInventory = specificInventory;
    }

    public Inventory getReferenceInventory() {
        return referenceInventory;
    }

    public Inventory getSpecificInventory() {
        return specificInventory;
    }

    public Inventory getMergedInventory() {
        return mergedInventory;
    }

    public void addCompareAttribute(String attribute, MergeFunction mergeFunction) {
        compareAttributes.put(attribute, mergeFunction);
    }

    public void addMergeByCsvAttribute(String attribute) {
        addCompareAttribute(attribute, CSV_MERGER);
    }

    public void loadVulnerabilityDataCompareAttributes() {
        addMergeByCsvAttribute(InventoryAttribute.ADDITIONAL_CPE.getKey());
        addMergeByCsvAttribute(InventoryAttribute.INAPPLICABLE_CPE.getKey());
        addMergeByCsvAttribute(InventoryAttribute.INITIAL_CPE_URIS.getKey());
        addMergeByCsvAttribute(InventoryAttribute.CVE_INDICATION.getKey());
        addMergeByCsvAttribute(InventoryAttribute.INAPPLICABLE_CVE.getKey());
        addMergeByCsvAttribute(InventoryAttribute.MS_PRODUCT_ID.getKey());
    }

    public Inventory merge(boolean addArtifactsFromSpecificInventory) {
        mergedInventory = new Inventory();
        if (referenceInventory == null || specificInventory == null) return mergedInventory;

        List differences = findDifferences(addArtifactsFromSpecificInventory);

        List mergedArtifacts = mergedInventory.getArtifacts();
        Set knownArtifacts = new HashSet<>();

        for (Difference difference : differences) {
            if (difference.artifactReference != null)
                knownArtifacts.add(difference.artifactReference);
            if (difference.artifactSpecific != null && addArtifactsFromSpecificInventory)
                knownArtifacts.add(difference.artifactSpecific);

            if (difference.type == DifferenceType.SPECIFIC_ARTIFACT_DOES_NOT_EXIST && difference.artifactReference != null) {
                // add the reference artifact
                Artifact mergedArtifact = new Artifact(difference.artifactReference);
                mergedArtifacts.add(mergedArtifact);

            } else if (addArtifactsFromSpecificInventory && difference.type == DifferenceType.REFERENCE_ARTIFACT_DOES_NOT_EXIST && difference.artifactSpecific != null) {
                // add the specific artifact
                Artifact mergedArtifact = new Artifact(difference.artifactSpecific);
                mergedArtifacts.add(mergedArtifact);

            } else if (difference.type == DifferenceType.REFERENCE_ATTRIBUTE_DOES_NOT_EXIST && difference.artifactSpecific != null) {
                // since the reference artifact does not have the attribute, use the specific artifact's attribute
                Artifact mergedArtifact = findOrCreateNew(difference.artifactReference, mergedInventory);
                mergedArtifact.set(difference.attribute, difference.artifactSpecific.get(difference.attribute));
                addIfNotContained(mergedArtifact, mergedArtifacts);

            } else if (difference.type == DifferenceType.SPECIFIC_ATTRIBUTE_DOES_NOT_EXIST || difference.type == DifferenceType.EQUAL) {
                // the information is already present in the reference artifact, ignore the specific artifact
                Artifact mergedArtifact = findOrCreateNew(difference.artifactReference, mergedInventory);
                addIfNotContained(mergedArtifact, mergedArtifacts);

            } else if (difference.type == DifferenceType.NOT_EQUAL) {
                // both values have to be merged, use the specified merge function to merge them
                String referenceValue = difference.artifactReference.get(difference.attribute);
                String specificValue = difference.artifactSpecific.get(difference.attribute);

                String mergedValue = compareAttributes.getOrDefault(difference.attribute, CSV_MERGER).merge(referenceValue, specificValue);

                Artifact mergedArtifact = findOrCreateNew(difference.artifactReference, mergedInventory);
                mergedArtifact.set(difference.attribute, mergedValue);
                addIfNotContained(mergedArtifact, mergedArtifacts);
            }
        }

        // add remaining artifacts
        knownArtifacts.addAll(mergedArtifacts);
        Set checkArtifacts = new HashSet<>(referenceInventory.getArtifacts());
        if (addArtifactsFromSpecificInventory) checkArtifacts.addAll(specificInventory.getArtifacts());
        for (Artifact checkArtifact : checkArtifacts) {
            if (knownArtifacts.contains(checkArtifact)) continue;
            mergedArtifacts.add(checkArtifact);
        }

        return mergedInventory;
    }

    private Artifact findOrCreateNew(Artifact reference, Inventory inventory) {
        if (inventory.getArtifacts().contains(reference)) return reference;
        Artifact checkArtifact = findArtifact(reference, inventory);
        if (checkArtifact != null) return checkArtifact;
        return new Artifact(reference);
    }

    private Artifact findArtifact(Artifact artifact, Inventory inventory) {
        // find by id and checksum
        //Artifact foundArtifact = inventory.findArtifactByIdAndChecksum(artifact.getId(), artifact.getChecksum());
        Artifact foundArtifact = findArtifactByIdChecksumVersion(inventory, artifact);

        // ... or by id only; if no checksum match was found
        //if (foundArtifact == null) foundArtifact = inventory.findArtifact(artifact.getId());

        return foundArtifact;
    }

    private Artifact findArtifactByIdChecksumVersion(Inventory inventory, Artifact checkArtifact) {
        for (Artifact candidate : inventory.getArtifacts()) {
            boolean idEquals = attributeEquals(checkArtifact, candidate, Artifact.Attribute.ID.getKey());
            boolean checksumEquals = attributeEquals(checkArtifact, candidate, Artifact.Attribute.CHECKSUM.getKey());
            boolean versionEquals = attributeEquals(checkArtifact, candidate, Artifact.Attribute.VERSION.getKey());
            if (idEquals && checksumEquals && versionEquals) return candidate;
        }
        return null;
    }

    private boolean attributeEquals(Artifact a1, Artifact a2, String attribute) {
        return a1.get(attribute) == null && a2.get(attribute) == null
                || a1.get(attribute) != null && a2.get(attribute) != null && a1.get(attribute).equals(a2.get(attribute));
    }

    private void addIfNotContained(Artifact reference, List artifacts) {
        if (!artifacts.contains(reference)) artifacts.add(reference);
    }

    /**
     * Finds all differences between the two inventories artifacts by the compare attributes set before.
     *
     * @return A list of all differences.
     */
    public List findDifferences() {
        return findDifferences(true);
    }

    /**
     * Finds all differences between the two inventories artifacts by the compare attributes set before.
     *
     * @param addArtifactsFromSpecificInventory Whether to include the artifacts from the specific inventory in the result.
     * @return A list of all differences.
     */
    public List findDifferences(boolean addArtifactsFromSpecificInventory) {
        List knownMergedArtifacts = new ArrayList<>();
        List differences = new ArrayList<>();

        // iterate over the specific artifacts and find the differences
        if (addArtifactsFromSpecificInventory)
            for (Artifact specificArtifact : specificInventory.getArtifacts()) {
                Artifact referenceArtifact = findArtifact(specificArtifact, referenceInventory);
                differences.addAll(findDifferences(referenceArtifact, specificArtifact));
                knownMergedArtifacts.add(specificArtifact);
                knownMergedArtifacts.add(referenceArtifact);
            }
        for (Artifact referenceArtifact : referenceInventory.getArtifacts()) {
            if (knownMergedArtifacts.contains(referenceArtifact)) continue;
            Artifact specificArtifact = findArtifact(referenceArtifact, specificInventory);
            differences.addAll(findDifferences(referenceArtifact, specificArtifact));
            knownMergedArtifacts.add(specificArtifact);
            knownMergedArtifacts.add(referenceArtifact);
        }

        return differences;
    }

    /**
     * Checks what the difference between the two given artifacts are on the given attributes.
* Checks for these conditions: *
    *
  • reference artifact is null
  • *
  • specific artifact is null
  • *
  • attributes are equal on both artifacts
  • *
  • attributes are not equal on both artifacts
  • *
  • specific artifact does not have attribute
  • *
  • reference artifact does not have attribute
  • *
* * @param referenceArtifact The reference artifact. * @param specificArtifact The specific artifact. * @return The list of differences. */ private List findDifferences(Artifact referenceArtifact, Artifact specificArtifact) { List differences = new ArrayList<>(); // check if one or both of the artifacts does not exist if (referenceArtifact == null && specificArtifact == null) { return differences; } else if (referenceArtifact == null) { differences.add(new Difference(DifferenceType.REFERENCE_ARTIFACT_DOES_NOT_EXIST, null, null, specificArtifact)); return differences; } else if (specificArtifact == null) { differences.add(new Difference(DifferenceType.SPECIFIC_ARTIFACT_DOES_NOT_EXIST, null, referenceArtifact, null)); return differences; } // check what the differences on the artifacts are for (String attribute : compareAttributes.keySet()) { String referenceValue = referenceArtifact.get(attribute); String specificValue = specificArtifact.get(attribute); if (referenceValue == null && specificValue == null) { differences.add(new Difference(DifferenceType.EQUAL, attribute, referenceArtifact, specificArtifact)); } else if (referenceValue == null) { differences.add(new Difference(DifferenceType.SPECIFIC_ATTRIBUTE_DOES_NOT_EXIST, attribute, referenceArtifact, specificArtifact)); } else if (specificValue == null) { differences.add(new Difference(DifferenceType.REFERENCE_ATTRIBUTE_DOES_NOT_EXIST, attribute, referenceArtifact, specificArtifact)); } else if (specificValue.equals(referenceValue)) { differences.add(new Difference(DifferenceType.EQUAL, attribute, referenceArtifact, specificArtifact)); } else { differences.add(new Difference(DifferenceType.NOT_EQUAL, attribute, referenceArtifact, specificArtifact)); } } return differences; } public JSONArray differencesToJson(List differences) { JSONArray differenceJson = new JSONArray(); differences.stream() .filter(Objects::nonNull) .filter(d -> d.type != DifferenceType.EQUAL) .map(Difference::toJson) .forEach(differenceJson::put); return differenceJson; } private static class Difference { private final DifferenceType type; private final String attribute; private final Artifact artifactReference; private final Artifact artifactSpecific; public Difference(DifferenceType type, String attribute, Artifact artifactReference, Artifact artifactSpecific) { this.type = type; this.attribute = attribute; this.artifactReference = artifactReference; this.artifactSpecific = artifactSpecific; } public DifferenceType getType() { return type; } public String getAttribute() { return attribute; } public Artifact getArtifactReference() { return artifactReference; } public Artifact getArtifactSpecific() { return artifactSpecific; } public JSONObject toJson() { JSONObject json = new JSONObject(); json.put("type", type.name()); json.put("attribute", attribute); if (artifactReference != null) { json.put("referenceId", artifactReference.getId()); json.put("referenceChecksum", artifactReference.getChecksum()); } if (artifactSpecific != null) { json.put("specificId", artifactSpecific.getId()); json.put("specificChecksum", artifactSpecific.getChecksum()); } return json; } @Override public String toString() { return "Difference{" + "type='" + type.name() + '\'' + ", attribute='" + attribute + '\'' + ", artifactReference=" + (artifactReference == null ? null : artifactReference.getId()) + ", artifactSpecific=" + (artifactSpecific == null ? null : artifactSpecific.getId()) + '}'; } } public enum DifferenceType { REFERENCE_ARTIFACT_DOES_NOT_EXIST, SPECIFIC_ARTIFACT_DOES_NOT_EXIST, REFERENCE_ATTRIBUTE_DOES_NOT_EXIST, SPECIFIC_ATTRIBUTE_DOES_NOT_EXIST, EQUAL, NOT_EQUAL } public interface MergeFunction { String merge(String referenceValue, String specificValue); } public final static MergeFunction CSV_MERGER = (referenceValue, specificValue) -> { Set mergedValues = new LinkedHashSet<>(); mergedValues.addAll(Arrays.stream(referenceValue.split(", ")).collect(Collectors.toList())); mergedValues.addAll(Arrays.stream(specificValue.split(", ")).collect(Collectors.toList())); return String.join(", ", mergedValues); }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy