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

com.metaeffekt.artifact.enrichment.validation.InventoryValidationEnrichment 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.enrichment.validation;

import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarningEntry;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarnings;
import com.metaeffekt.artifact.enrichment.InventoryEnricher;
import com.metaeffekt.artifact.enrichment.configurations.InventoryValidationEnrichmentConfiguration;
import com.metaeffekt.artifact.enrichment.validation.reason.InventoryValidationReason;
import com.metaeffekt.artifact.enrichment.validation.reason.ReasonIdentifier;
import com.metaeffekt.mirror.download.documentation.EnricherMetadata;
import com.metaeffekt.mirror.download.documentation.InventoryEnrichmentPhase;
import lombok.Setter;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@EnricherMetadata(
        name = "Inventory Validation", phase = InventoryEnrichmentPhase.INVENTORY_POST_PROCESSING,
        intermediateFileSuffix = "inventory-validation", mavenPropertyName = "inventoryValidationEnrichment",
        shouldWriteIntermediateInventory = false
)
public class InventoryValidationEnrichment extends InventoryEnricher {

    private static final Logger LOG = LoggerFactory.getLogger(InventoryValidationEnrichment.class);

    @Setter
    private InventoryValidationEnrichmentConfiguration configuration = new InventoryValidationEnrichmentConfiguration();

    private final Map> cachedValidationFailReasons = new HashMap<>();
    private final File baseMirrorDirectory;

    public InventoryValidationEnrichment(File baseMirrorDirectory) {
        this.baseMirrorDirectory = baseMirrorDirectory;
    }

    @Override
    public InventoryValidationEnrichmentConfiguration getConfiguration() {
        return configuration;
    }

    public List getValidationFailReasons(Inventory inventory) {
        return cachedValidationFailReasons.get(inventory);
    }

    @Override
    protected void performEnrichment(Inventory inventory) {
        final List validators = configuration.buildValidators();

        final Map> validationErrorReasons = new LinkedHashMap<>();

        for (InventoryValidator validator : validators) {
            validator.beforeValidation(baseMirrorDirectory);

            LOG.info("Validating inventory with validator: [{}]", validator.getValidatorName());

            try {
                final List reasons = validator.validate(inventory);

                for (InventoryValidationReason reason : reasons) {
                    validationErrorReasons.computeIfAbsent(validator, k -> new ArrayList<>()).add(reason);
                }
            } catch (Exception e) {
                throw new IllegalStateException("Performing the validation failed for validator " + validator.getValidatorName() + ": " + e.getMessage(), e);
            }

            if (validationErrorReasons.getOrDefault(validator, Collections.emptyList()).isEmpty()) {
                LOG.info("No validation failures found");
            } else {
                LOG.warn("Found [{}] validation failures", validationErrorReasons.values().stream().mapToInt(List::size).sum());
            }
        }

        cachedValidationFailReasons.computeIfAbsent(inventory, k -> new ArrayList<>())
                .addAll(validationErrorReasons.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));


        if (!validationErrorReasons.isEmpty()) {
            final int count = validationErrorReasons.values().stream().mapToInt(List::size).sum();
            final String longDescriptions = buildValidationLongDescriptions(Collections.singletonList(validationErrorReasons));

            final String logMessage = "Inventory Validation failures in " + count + " instance" + (count == 1 ? "" : "s") + ":\n" +
                    buildValidationFailReasonsReport(validationErrorReasons) +
                    "\n" + longDescriptions + "\n";

            if (configuration.isFailOnValidationErrors()) {
                throw new IllegalStateException(logMessage);
            } else {
                if (configuration.isAddAsCorrelationWarnings()) {
                    addInventoryWarningsToInventory(inventory, cachedValidationFailReasons.get(inventory));
                }
                LOG.error(logMessage);
            }
        } else {
            LOG.info("Inventory Validation successful.");
        }
    }

    private String buildValidationLongDescriptions(List>> validationFailReasons) {
        final Set reasonIdentifiers = new HashSet<>();

        for (Map> validationFailReason : validationFailReasons) {
            for (List reasons : validationFailReason.values()) {
                for (InventoryValidationReason reason : reasons) {
                    if (reason.getReason().getLongDescription() != null) {
                        reasonIdentifiers.add(reason.getReason());
                    }
                }
            }
        }

        if (reasonIdentifiers.isEmpty()) {
            return "";
        }

        final StringBuilder joiner = new StringBuilder();
        joiner.append("More information about the errors and possible solutions below:\n");
        for (ReasonIdentifier reasonIdentifier : reasonIdentifiers.stream().sorted(Comparator.comparingInt(Enum::ordinal)).collect(Collectors.toList())) {
            joiner.append(makeIndentedListEntry(reasonIdentifier.getLongDescription(), "[" + reasonIdentifier.ordinal() + "] ", 1));
        }

        return joiner.toString();
    }

    private String buildValidationFailReasonsReport(Map> validationFailReasons) {
        final StringBuilder sb = new StringBuilder();

        for (Map.Entry> entry : validationFailReasons.entrySet()) {
            final InventoryValidator validator = entry.getKey();
            final List reasons = entry.getValue();

            sb.append("\n").append(formatLogHeader(validator.getValidatorName())).append("\n");

            for (InventoryValidationReason reason : reasons) {
                sb.append(makeIndentedListEntry(reason.toString(), "- ", 2));
            }
        }

        return sb.toString();
    }

    private void addInventoryWarningsToInventory(Inventory inventory, List validationFailReasons) {
        final InventoryWarnings inventoryWarnings = new InventoryWarnings(inventory);

        for (InventoryValidationReason validationFailReason : validationFailReasons) {
            final AbstractModelBase source = validationFailReason.getSource();
            final String warningText = validationFailReason.getReason().getShortDescription() + ": " + validationFailReason.getText();

            if (source instanceof Artifact) {
                inventoryWarnings.addArtifactWarning(new InventoryWarningEntry<>((Artifact) source, warningText, "Validation"));
            } else if (source instanceof VulnerabilityMetaData) {
                inventoryWarnings.addVulnerabilityWarning(new InventoryWarningEntry<>((VulnerabilityMetaData) source, warningText, "Validation"));
            } else if (source == null) {
                inventoryWarnings.addSourcelessWarning(warningText);
            } else {
                LOG.warn("Cannot add correlation warning for source of type [{}]: {}", source, warningText);
            }
        }
    }

    /**
     * Makes a string:
* *     line1
*     line2
*
* into a list:
* *     - line1
*       line2
*
* * @param text The text to convert. * @param indentationSymbol The symbol to use for indentation. * @param indent The number of spaces to indent the list entry. * @return The converted text. */ private String makeIndentedListEntry(String text, String indentationSymbol, int indent) { final String[] lines = text.split("\n"); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < lines.length; i++) { if (i == 0) { sb.append(repeat(" ", indent)).append(indentationSymbol).append(lines[i]).append("\n"); } else { sb.append(repeat(" ", indent + indentationSymbol.length())).append(lines[i]).append("\n"); } } return sb.toString(); } private String repeat(String text, int count) { return IntStream.range(0, count).mapToObj(i -> text).collect(Collectors.joining()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy