com.metaeffekt.mirror.query.MsrcKbChainIndexQuery 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.mirror.query;
import com.metaeffekt.artifact.analysis.utils.CustomCollectors;
import com.metaeffekt.artifact.analysis.utils.LruLinkedHashMap;
import com.metaeffekt.mirror.contents.msrcdata.MsrcSupersedeNode;
import com.metaeffekt.mirror.contents.msrcdata.MsrcSupersedeNodeRelations;
import com.metaeffekt.mirror.index.Index;
import com.metaeffekt.mirror.index.IndexSearch;
import com.metaeffekt.mirror.index.advisor.MsrcKbChainIndex;
import org.apache.lucene.document.Document;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* When using the methods of this class, the nodes will be loaded lazily, meaning when accessing a specific KB Id, only
* the node with that KB Id will be loaded properly. The references to directly neighboring nodes will be created as
* well, but the further away the nodes are, the more incomplete they will be to improve performance.
* In order to be able to trust the relations of the nodes by more than one level, the {@link #preloadAllNodes()}
* method can be used to load all nodes.
*/
public class MsrcKbChainIndexQuery extends IndexQuery {
private final Map cachedMsrcNodes = new ConcurrentHashMap<>();
private final static int MAX_CACHED_VULNERABILITY_TO_NODES = 1000;
private final Map> cachedVulnerabilityToNodes = new LruLinkedHashMap<>(MAX_CACHED_VULNERABILITY_TO_NODES);
/**
* On average, this cache has a hit rate of 99.78%. with a size of 100:
* Cache misses: 100, hits: 45158, ratio: 99.77904458880198
* Used by {@link #findNodesSupersedingKbId(String, String)}.
*/
private final static int MAX_CACHED_SUPERSEDING_KB_TO_NODES = 100;
private final Map> cachedSupersedingKbToNodes = new LruLinkedHashMap<>(MAX_CACHED_SUPERSEDING_KB_TO_NODES);
public MsrcKbChainIndexQuery(File baseMirrorDirectory) {
super(baseMirrorDirectory, MsrcKbChainIndex.class);
}
public MsrcKbChainIndexQuery(Index index) {
super(index);
}
public MsrcSupersedeNode findNodeByKbId(String kbId) {
synchronized (cachedMsrcNodes) { // see MsrcSupersedeNode#fromDocument
if (cachedMsrcNodes.containsKey(kbId)) {
return cachedMsrcNodes.get(kbId);
}
}
final List nodes = createNodes(super.index.findDocuments(new IndexSearch().fieldContains("kbId", kbId)));
nodes.removeIf(node -> !node.getKbId().equals(kbId));
if (nodes.size() > 1) {
throw new IllegalStateException("Found more than one node for kbId [" + kbId + "]: " + nodes.stream().map(MsrcSupersedeNode::toJson).collect(CustomCollectors.toJsonArray()));
} else if (nodes.size() == 1) {
return nodes.get(0);
} else {
return null;
}
}
public List findNodesSupersedingKbId(String supersededKbId) {
return findNodesSupersedingKbId(supersededKbId, null);
}
public List findNodesSupersedingKbId(String supersededKbId, String productId) {
final String cacheKey = supersededKbId + (productId != null ? "-c-" + productId : "");
if (cachedSupersedingKbToNodes.containsKey(cacheKey)) {
return new ArrayList<>(cachedSupersedingKbToNodes.get(cacheKey));
}
final Set relationNodes = new HashSet<>(findNodesInRelations(supersededKbId));
if (productId != null) {
relationNodes.removeIf(node -> !node.containsSupersededKbId(supersededKbId, productId));
} else {
relationNodes.removeIf(node -> !node.containsSupersededKbId(supersededKbId));
}
for (MsrcSupersedeNode node : new HashSet<>(relationNodes)) {
relationNodes.addAll(findNodesSupersedingKbId(node.getKbId(), productId));
}
cachedSupersedingKbToNodes.put(cacheKey, new ArrayList<>(relationNodes));
return new ArrayList<>(relationNodes);
}
public List findNodesSupersededByKbId(String supersededKbId) {
return findNodesSupersededByKbId(supersededKbId, null);
}
public List findNodesSupersededByKbId(String supersededKbId, String productId) {
final Set relationNodes = new HashSet<>(findNodesInRelations(supersededKbId));
if (productId != null) {
relationNodes.removeIf(node -> !node.containsSupersededByKbId(supersededKbId, productId));
} else {
relationNodes.removeIf(node -> !node.containsSupersededByKbId(supersededKbId));
}
for (MsrcSupersedeNode node : new HashSet<>(relationNodes)) {
relationNodes.addAll(findNodesSupersededByKbId(node.getKbId(), productId));
}
return new ArrayList<>(relationNodes);
}
public List findNodesByVulnerability(String vulnerability) {
return findNodesByVulnerability(vulnerability, null);
}
public List findNodesByVulnerability(String vulnerability, String productId) {
final String cacheKey = vulnerability + (productId != null ? "-c-" + productId : "");
if (cachedVulnerabilityToNodes.containsKey(cacheKey)) {
return cachedVulnerabilityToNodes.get(cacheKey);
}
final List relationNodes = findNodesInRelations(vulnerability);
if (productId != null) {
relationNodes.removeIf(node -> !node.containsVulnerability(vulnerability, productId));
} else {
relationNodes.removeIf(node -> !node.containsVulnerability(vulnerability));
}
cachedVulnerabilityToNodes.put(cacheKey, relationNodes);
return relationNodes;
}
public Set findFixingKbIds(String vulnerability) {
return findFixingKbIds(vulnerability, null);
}
public Set findFixingKbIds(String vulnerability, String productId) {
final Set kbIds = new HashSet<>(findNodesByVulnerability(vulnerability, productId));
final Set toCheckKbIds = new HashSet<>();
for (MsrcSupersedeNode node : kbIds) {
toCheckKbIds.addAll(findNodesSupersedingKbId(node.getKbId(), productId));
}
final Set checkedIds = new HashSet<>();
while (!toCheckKbIds.isEmpty()) {
final MsrcSupersedeNode node = toCheckKbIds.iterator().next();
toCheckKbIds.remove(node);
checkedIds.add(node);
kbIds.add(node);
toCheckKbIds.addAll(findNodesSupersedingKbId(node.getKbId(), productId));
toCheckKbIds.removeAll(checkedIds);
}
return kbIds;
}
/**
* Builds a data structure that allows for fast traversal of the KB nodes.
* The structure is a map where each key is a KB node and its value is a set of related KB nodes.
*
* @param kbNodes The collection of KB nodes to build the tree from.
* @param productId The product ID to filter the relationships with, must not be null.
* @param limitToInputNodes Specifies whether to limit the relationships to only input nodes (kbNodes) or not.
* @return A map representing the tree structure, where each key is a KB node and its value is a set of related KB nodes.
*/
public Map> buildTree(Collection kbNodes, String productId, boolean limitToInputNodes) {
final Map> fixingKbIdsTree = new HashMap<>();
for (MsrcSupersedeNode checkNode : kbNodes) {
final MsrcSupersedeNodeRelations relations = checkNode.getProductRelationsByProduct(productId);
if (relations == null) continue;
final Set supersedenceIds = fixingKbIdsTree.computeIfAbsent(checkNode, k -> new HashSet<>());
for (MsrcSupersedeNode node : relations.getSupersedes()) {
final boolean allowedToAdd = !limitToInputNodes || kbNodes.contains(node);
if (allowedToAdd) supersedenceIds.add(node);
}
for (MsrcSupersedeNode node : relations.getSupersededBy()) {
final boolean allowedToAdd = !limitToInputNodes || kbNodes.contains(node);
if (allowedToAdd) {
final Set supersededBy = fixingKbIdsTree.computeIfAbsent(node, k -> new HashSet<>());
supersededBy.add(checkNode);
}
}
}
return fixingKbIdsTree;
}
public List findNodesByProductId(String productId) {
final List relationNodes = findNodesInRelations(productId);
relationNodes.removeIf(node -> node.getProductRelationsByProduct(productId) == null);
return relationNodes;
}
public List findVulnerabilitiesByProductId(String productId) {
return findVulnerabilitiesByProductId(productId, Collections.emptyList());
}
public List findVulnerabilitiesByProductId(String productId, Collection kbIds) {
final Set vulnerabilities = new HashSet<>();
for (MsrcSupersedeNode node : findNodesByProductId(productId)) {
vulnerabilities.addAll(node.getProductRelationsByProduct(productId).getAffectsVulnerabilities());
}
if (kbIds.isEmpty()) return new ArrayList<>(vulnerabilities);
final Map> vulnerabilitiesFixedByKb = new HashMap<>();
for (String vulnerability : vulnerabilities) {
final Set fixingKbIds = findFixingKbIds(vulnerability, productId);
for (MsrcSupersedeNode fixingKbId : fixingKbIds) {
vulnerabilitiesFixedByKb.computeIfAbsent(vulnerability, k -> new HashSet<>()).add(fixingKbId.getKbId());
}
}
final Set fixedVulnerabilities = new HashSet<>();
for (Map.Entry> entry : vulnerabilitiesFixedByKb.entrySet()) {
if (kbIds.stream().anyMatch(fixedKb -> entry.getValue().contains(fixedKb))) {
fixedVulnerabilities.add(entry.getKey());
}
}
final List remainingVulnerabilities = new ArrayList<>(vulnerabilities);
remainingVulnerabilities.removeAll(fixedVulnerabilities);
return remainingVulnerabilities;
}
public boolean isVulnerabilityFixed(String vulnerability, String productId, Collection activeKbIds) {
if (activeKbIds.isEmpty()) return false;
final Set fixingKbIds = findFixingKbIds(vulnerability, productId);
// contains any
for (MsrcSupersedeNode fixingKbId : fixingKbIds) {
if (activeKbIds.contains(fixingKbId.getKbId())) {
return true;
}
}
return false;
}
public void collectSupersedingKbIdentifiers(Set vulnerabilities, String productId, Collection appendKbIdsTo) {
for (String vulnerabilityId : vulnerabilities) {
final Set fixingKbIds = findFixingKbIds(vulnerabilityId, productId);
boolean foundNew;
do {
foundNew = false;
for (MsrcSupersedeNode fixingKbId : fixingKbIds) {
for (String appliedMsKbIdentifier : new HashSet<>(appendKbIdsTo)) {
if (fixingKbId.containsSupersededByKbId(appliedMsKbIdentifier)) {
if (appendKbIdsTo.add(fixingKbId.getKbId())) {
foundNew = true;
}
}
}
}
} while (foundNew);
}
}
public List findAllNodes() {
return createNodes(super.index.findAllDocuments());
}
public void preloadAllNodes() {
findAllNodes();
}
private List findNodesInRelations(String searchText) {
final List documents = super.index.findDocuments(new IndexSearch().fieldContains("rel", searchText));
return createNodes(documents);
}
private List createNodes(List documents) {
return documents.stream()
.map((Document document) -> MsrcSupersedeNode.fromDocument(document, cachedMsrcNodes))
.collect(Collectors.toList());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy