com.metaeffekt.artifact.analysis.utils.InventoryMergeUtils 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 org.apache.commons.lang3.StringUtils;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.LicenseData;
import org.metaeffekt.core.inventory.processor.reader.InventoryReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
// FIXME: check difference between core version; eliminate
@Deprecated // use core implementation
public class InventoryMergeUtils {
private static final Logger LOG = LoggerFactory.getLogger(InventoryMergeUtils.class);
protected boolean addDefaultArtifactExcludedAttributes = true;
protected Set artifactExcludedAttributes = new HashSet<>();
protected boolean addDefaultArtifactMergeAttributes = true;
protected Set artifactMergeAttributes = new HashSet<>();
// NOTE:
// - we merge artifacts
// - we merge assets (currently only by adding; not merging; not cleaning duplicates)
// - we merge license data (resolving duplicates; merging on attribute level; input inventors should be
// consistent and up-to-date)
// TODO: revise the following once assets have been fully established
// - we do not merge component patterns; the target component patterns are preserved (future: tbc)
// - we do not merge license notices; merges are considered to use reference inventory as targets (future: merge and remove duplicates)
// - we do not merge vulnerabilities; vulnerabilities are in the reference inventory (target) or
// processed later on (future: organize vulnerability data per asset, merge details on artifact level)
public void merge(List sourceInventories, Inventory targetInventory) throws IOException {
// parse and merge the collected inventories
for (File sourceInventoryFile : sourceInventories) {
final Inventory sourceInventory = new InventoryReader().readInventory(sourceInventoryFile);
// merge and manage artifacts
mergeArtifacts(sourceInventory, targetInventory);
// simply add asset data (anticipating there are no duplicates)
mergeAssetMetaData(sourceInventory, targetInventory);
// merge license data
mergeLicenseData(sourceInventory, targetInventory);
}
}
private void mergeArtifacts(Inventory sourceInv, Inventory targetInv) {
// complete artifacts in targetInv with checksums (with matching project location)
// NOTE: this assumes that the source inventory container extensions to the target inventory
for (final Artifact artifact : targetInv.getArtifacts()) {
// existing checksums must not be overwritten
if (StringUtils.isNotBlank(artifact.getChecksum())) continue;
final List candidates = sourceInv.findAllWithId(artifact.getId());
// matches match on project location
final List matches = new ArrayList<>();
for (final Artifact candidate : candidates) {
if (matchesProjects(artifact, candidate)) {
matches.add(candidate);
}
}
for (final Artifact match : matches) {
artifact.setChecksum(match.getChecksum());
}
}
// add NOT COVERED artifacts from sourceInv in targetInv using id and checksum
for (final Artifact artifact : sourceInv.getArtifacts()) {
final Artifact candidate = targetInv.findArtifactByIdAndChecksum(artifact.getId(), artifact.getChecksum());
if (candidate == null) {
targetInv.getArtifacts().add(artifact);
}
}
// remove duplicates (in the sense of exclude and merge attributes
final Set toBeDeleted = new HashSet<>();
final Map representationArtifactMap = new HashMap<>();
final Set attributes = new HashSet<>();
final Set excludedAttributes = new HashSet<>(artifactExcludedAttributes);
if (addDefaultArtifactExcludedAttributes) {
excludedAttributes.add("Verified");
excludedAttributes.add("Archive Path");
excludedAttributes.add("Latest Version");
excludedAttributes.add("Security Relevance");
excludedAttributes.add("Security Category");
excludedAttributes.add("WILDCARD-MATCH");
}
// currently not configurable as these require specialized implementations
final Set mergeAttributes = new HashSet<>(artifactMergeAttributes);
if (addDefaultArtifactMergeAttributes) {
mergeAttributes.add("Projects");
mergeAttributes.add("Source Project");
}
// compile attributes list covering all artifacts / clear excluded attributes
for (final Artifact artifact : targetInv.getArtifacts()) {
// the excluded attributes are eliminated; merge attributes persist (as these are required)
for (final String attribute : excludedAttributes) {
artifact.set(attribute, null);
}
// add the (remaining) attributes to the overall list
attributes.addAll(artifact.getAttributes());
}
// the merge attributes do not contribute to the artifacts representation
attributes.removeAll(mergeAttributes);
// produce an ordered list of the attributes
final List attributesOrdered = new ArrayList<>(attributes);
attributesOrdered.sort(String::compareToIgnoreCase);
for (final Artifact artifact : targetInv.getArtifacts()) {
final StringBuilder stringRepresentation = new StringBuilder();
for (String attribute : attributes) {
if (stringRepresentation.length() == 0) {
stringRepresentation.append(";");
}
stringRepresentation.append(attribute).append("=").append(artifact.get(attribute));
}
final String rep = stringRepresentation.toString();
if (representationArtifactMap.containsKey(rep)) {
toBeDeleted.add(artifact);
final Artifact retainedArtifact = representationArtifactMap.get(rep);
for (final String key : mergeAttributes) {
retainedArtifact.append(key, artifact.get(key), ", ");
}
} else {
representationArtifactMap.put(rep, artifact);
}
}
targetInv.getArtifacts().removeAll(toBeDeleted);
}
private static void mergeAssetMetaData(Inventory sourceInv, Inventory targetInv) {
targetInv.getAssetMetaData().addAll(sourceInv.getAssetMetaData());
}
private void mergeLicenseData(Inventory sourceInv, Inventory targetInv) {
// build map
final Map canonicalNameLicenseDataMap = new HashMap<>();
for (LicenseData licenseData : targetInv.getLicenseData()) {
canonicalNameLicenseDataMap.put(licenseData.get(LicenseData.Attribute.CANONICAL_NAME), licenseData);
}
// compare, merge or insert
for (LicenseData sourceLicenseData : sourceInv.getLicenseData()) {
final String canonicalName = sourceLicenseData.get(LicenseData.Attribute.CANONICAL_NAME);
final LicenseData targetLicenseData = canonicalNameLicenseDataMap.get(canonicalName);
if (targetLicenseData != null) {
targetLicenseData.merge(sourceLicenseData);
} else {
// does not exist in targetInv yet; add
targetInv.getLicenseData().add(sourceLicenseData);
// manage map
canonicalNameLicenseDataMap.put(canonicalName, sourceLicenseData);
}
}
}
private boolean matchesProjects(Artifact artifact, Artifact candidate) {
for (String location : artifact.getProjects()) {
for (String candidateLocation : candidate.getProjects()) {
if (candidateLocation.contains(location)) {
return true;
}
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy