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

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