com.metaeffekt.artifact.analysis.utils.InventoryDiffMerger 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.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