ShapeValidationContainer.java

/*******************************************************************************
 * Copyright (c) 2023 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 ******************************************************************************/

package org.eclipse.rdf4j.sail.shacl;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.sail.SailException;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.SingleCloseablePlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationExecutionLogger;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
import org.eclipse.rdf4j.sail.shacl.results.lazy.ValidationResultIterator;
import org.slf4j.Logger;

class ShapeValidationContainer {
	private final Shape shape;
	private final boolean logValidationViolations;
	private final PlanNode planNode;
	private final ValidationExecutionLogger validationExecutionLogger;
	private final long effectiveValidationResultsLimitPerConstraint;
	private final boolean performanceLogging;
	private final Logger logger;

	public ShapeValidationContainer(Shape shape, Supplier<PlanNode> planNodeSupplier, boolean logValidationExecution,
			boolean logValidationViolations, long effectiveValidationResultsLimitPerConstraint,
			boolean performanceLogging, Logger logger) {
		this.shape = shape;
		this.logValidationViolations = logValidationViolations;
		this.effectiveValidationResultsLimitPerConstraint = effectiveValidationResultsLimitPerConstraint;
		this.performanceLogging = performanceLogging;
		this.logger = logger;
		try {
			PlanNode planNode = planNodeSupplier.get();
			this.validationExecutionLogger = ValidationExecutionLogger
					.getInstance(logValidationExecution);
			if (!(planNode.isGuaranteedEmpty())) {
				assert planNode instanceof SingleCloseablePlanNode;
				planNode.receiveLogger(validationExecutionLogger);
				this.planNode = planNode;
			} else {
				this.planNode = planNode;
			}
		} catch (Exception e) {
			throw new SailException("Error processing SHACL Shape " + shape.getId() + "\n" + shape, e);
		}

	}

	public Shape getShape() {
		return shape;
	}

	public boolean hasPlanNode() {
		return !(planNode.isGuaranteedEmpty());
	}

	public ValidationResultIterator performValidation() {
		long before = getTimeStamp();
		handlePreLogging();

		ValidationResultIterator validationResults = null;

		try (CloseableIteration<? extends ValidationTuple, SailException> iterator = planNode.iterator()) {
			validationResults = new ValidationResultIterator(iterator, effectiveValidationResultsLimitPerConstraint);
			return validationResults;
		} catch (Exception e) {
			throw new SailException("Error validating SHACL Shape " + shape.getId() + "\n" + shape, e);
		} finally {
			handlePostLogging(before, validationResults);
		}
	}

	private long getTimeStamp() {
		if (performanceLogging) {
			return System.currentTimeMillis();
		}
		return 0;
	}

	private void handlePreLogging() {
		if (validationExecutionLogger.isEnabled()) {
			logger.info("Start execution of plan:\n{}\n", getShape().toString());
		}
	}

	private void handlePostLogging(long before, ValidationResultIterator validationResults) {
		if (validationExecutionLogger.isEnabled()) {
			validationExecutionLogger.flush();
		}

		if (validationResults != null) {

			if (performanceLogging) {
				long after = System.currentTimeMillis();
				logger.info("Execution of plan took {} ms for:\n{}\n",
						(after - before),
						getShape().toString());
			}

			if (validationExecutionLogger.isEnabled()) {
				logger.info("Finished execution of plan:\n{}\n",
						getShape().toString());
			}

			if (logValidationViolations) {
				if (!validationResults.conforms()) {
					List<ValidationTuple> tuples = validationResults.getTuples();

					logger.info(
							"SHACL not valid. The following experimental debug results were produced:\n\t\t{}\n\n{}\n",
							tuples.stream()
									.map(ValidationTuple::toString)
									.collect(Collectors.joining("\n\t\t")),
							getShape().toString()

					);
				}
			}

		}

	}

}