ShaclSailConfig.java

/*******************************************************************************
 * Copyright (c) 2019 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.config;

import static org.eclipse.rdf4j.model.util.Values.literal;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.CACHE_SELECT_NODES;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.GLOBAL_LOG_VALIDATION_EXECUTION;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.LOG_VALIDATION_PLANS;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.LOG_VALIDATION_VIOLATIONS;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.PARALLEL_VALIDATION;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.PERFORMANCE_LOGGING;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.RDFS_SUB_CLASS_REASONING;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.SERIALIZABLE_VALIDATION;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.SHAPES_GRAPH;
import static org.eclipse.rdf4j.sail.shacl.config.ShaclSailSchema.VALIDATION_ENABLED;

import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.common.annotation.Experimental;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
import org.eclipse.rdf4j.model.util.Configurations;
import org.eclipse.rdf4j.model.vocabulary.CONFIG;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.sail.config.AbstractDelegatingSailImplConfig;
import org.eclipse.rdf4j.sail.config.SailConfigException;
import org.eclipse.rdf4j.sail.config.SailImplConfig;
import org.eclipse.rdf4j.sail.shacl.ShaclSail;

/**
 * A {@link SailImplConfig} for {@link ShaclSail} configuration.
 *
 * @author Jeen Broekstra
 */
public class ShaclSailConfig extends AbstractDelegatingSailImplConfig {

	public static final boolean PARALLEL_VALIDATION_DEFAULT = true;
	public static final boolean LOG_VALIDATION_PLANS_DEFAULT = false;
	public static final boolean LOG_VALIDATION_VIOLATIONS_DEFAULT = false;
	public static final boolean VALIDATION_ENABLED_DEFAULT = true;
	public static final boolean CACHE_SELECT_NODES_DEFAULT = true;
	public static final boolean GLOBAL_LOG_VALIDATION_EXECUTION_DEFAULT = false;
	public static final boolean RDFS_SUB_CLASS_REASONING_DEFAULT = true;
	public static final boolean PERFORMANCE_LOGGING_DEFAULT = false;
	public static final boolean SERIALIZABLE_VALIDATION_DEFAULT = true;
	public static final boolean ECLIPSE_RDF4J_SHACL_EXTENSIONS_DEFAULT = false;
	public static final boolean DASH_DATA_SHAPES_DEFAULT = false;
	public final static long VALIDATION_RESULTS_LIMIT_TOTAL_DEFAULT = 1_000_000;
	public final static long VALIDATION_RESULTS_LIMIT_PER_CONSTRAINT_DEFAULT = 1_000;
	public final static long TRANSACTIONAL_VALIDATION_LIMIT_DEFAULT = 500_000;
	public final static Set<IRI> SHAPES_GRAPHS_DEFAULT = Set.of(RDF4J.SHACL_SHAPE_GRAPH);

	private boolean parallelValidation = PARALLEL_VALIDATION_DEFAULT;
	private boolean logValidationPlans = LOG_VALIDATION_PLANS_DEFAULT;
	private boolean logValidationViolations = LOG_VALIDATION_VIOLATIONS_DEFAULT;
	private boolean validationEnabled = VALIDATION_ENABLED_DEFAULT;
	private boolean cacheSelectNodes = CACHE_SELECT_NODES_DEFAULT;
	private boolean globalLogValidationExecution = GLOBAL_LOG_VALIDATION_EXECUTION_DEFAULT;
	private boolean rdfsSubClassReasoning = RDFS_SUB_CLASS_REASONING_DEFAULT;
	private boolean performanceLogging = PERFORMANCE_LOGGING_DEFAULT;
	private boolean serializableValidation = SERIALIZABLE_VALIDATION_DEFAULT;
	private boolean eclipseRdf4jShaclExtensions = ECLIPSE_RDF4J_SHACL_EXTENSIONS_DEFAULT;
	private boolean dashDataShapes = DASH_DATA_SHAPES_DEFAULT;
	private long validationResultsLimitTotal = VALIDATION_RESULTS_LIMIT_TOTAL_DEFAULT;
	private long validationResultsLimitPerConstraint = VALIDATION_RESULTS_LIMIT_PER_CONSTRAINT_DEFAULT;
	private long transactionalValidationLimit = TRANSACTIONAL_VALIDATION_LIMIT_DEFAULT;
	private Set<IRI> shapesGraphs = SHAPES_GRAPHS_DEFAULT;

	public ShaclSailConfig() {
		super(ShaclSailFactory.SAIL_TYPE);
	}

	public ShaclSailConfig(SailImplConfig delegate) {
		super(ShaclSailFactory.SAIL_TYPE, delegate);
	}

	public boolean isLogValidationPlans() {
		return logValidationPlans;
	}

	public void setLogValidationPlans(boolean logValidationPlans) {
		this.logValidationPlans = logValidationPlans;
	}

	public boolean isLogValidationViolations() {
		return logValidationViolations;
	}

	public void setLogValidationViolations(boolean logValidationViolations) {
		this.logValidationViolations = logValidationViolations;
	}

	public boolean isGlobalLogValidationExecution() {
		return globalLogValidationExecution;
	}

	public void setGlobalLogValidationExecution(boolean globalLogValidationExecution) {
		this.globalLogValidationExecution = globalLogValidationExecution;
	}

	public boolean isValidationEnabled() {
		return validationEnabled;
	}

	public void setValidationEnabled(boolean validationEnabled) {
		this.validationEnabled = validationEnabled;
	}

	public boolean isParallelValidation() {
		return parallelValidation;
	}

	public void setParallelValidation(boolean parallelValidation) {
		this.parallelValidation = parallelValidation;
	}

	public boolean isCacheSelectNodes() {
		return cacheSelectNodes;
	}

	public void setCacheSelectNodes(boolean cacheSelectNodes) {
		this.cacheSelectNodes = cacheSelectNodes;
	}

	public boolean isRdfsSubClassReasoning() {
		return rdfsSubClassReasoning;
	}

	public void setRdfsSubClassReasoning(boolean rdfsSubClassReasoning) {
		this.rdfsSubClassReasoning = rdfsSubClassReasoning;
	}

	public boolean isPerformanceLogging() {
		return performanceLogging;
	}

	public void setPerformanceLogging(boolean performanceLogging) {
		this.performanceLogging = performanceLogging;
	}

	public boolean isSerializableValidation() {
		return serializableValidation;
	}

	public void setSerializableValidation(boolean serializableValidation) {
		this.serializableValidation = serializableValidation;
	}

	@Experimental
	public boolean isEclipseRdf4jShaclExtensions() {
		return eclipseRdf4jShaclExtensions;
	}

	@Experimental
	public void setEclipseRdf4jShaclExtensions(boolean eclipseRdf4jShaclExtensions) {
		this.eclipseRdf4jShaclExtensions = eclipseRdf4jShaclExtensions;
	}

	@Experimental
	public boolean isDashDataShapes() {
		return dashDataShapes;
	}

	@Experimental
	public void setDashDataShapes(boolean dashDataShapes) {
		this.dashDataShapes = dashDataShapes;
	}

	public long getValidationResultsLimitTotal() {
		return validationResultsLimitTotal;
	}

	public long getValidationResultsLimitPerConstraint() {
		return validationResultsLimitPerConstraint;
	}

	public void setValidationResultsLimitTotal(long validationResultsLimitTotal) {
		this.validationResultsLimitTotal = validationResultsLimitTotal;
	}

	public void setValidationResultsLimitPerConstraint(long validationResultsLimitPerConstraint) {
		this.validationResultsLimitPerConstraint = validationResultsLimitPerConstraint;
	}

	public long getTransactionalValidationLimit() {
		return transactionalValidationLimit;
	}

	public void setTransactionalValidationLimit(long transactionalValidationLimit) {
		this.transactionalValidationLimit = transactionalValidationLimit;
	}

	public Set<IRI> getShapesGraphs() {
		return shapesGraphs;
	}

	public void setShapesGraphs(Set<IRI> shapesGraphs) {
		this.shapesGraphs = shapesGraphs;
	}

	@Override
	public Resource export(Model m) {
		Resource implNode = super.export(m);
		m.setNamespace(CONFIG.NS);

		m.add(implNode, CONFIG.Shacl.parallelValidation, BooleanLiteral.valueOf(isParallelValidation()));
		m.add(implNode, CONFIG.Shacl.logValidationPlans, BooleanLiteral.valueOf(isLogValidationPlans()));
		m.add(implNode, CONFIG.Shacl.logValidationViolations, BooleanLiteral.valueOf(isLogValidationViolations()));
		m.add(implNode, CONFIG.Shacl.validationEnabled, BooleanLiteral.valueOf(isValidationEnabled()));
		m.add(implNode, CONFIG.Shacl.cacheSelectNodes, BooleanLiteral.valueOf(isCacheSelectNodes()));
		m.add(implNode, CONFIG.Shacl.globalLogValidationExecution,
				BooleanLiteral.valueOf(isGlobalLogValidationExecution()));
		m.add(implNode, CONFIG.Shacl.rdfsSubClassReasoning, BooleanLiteral.valueOf(isRdfsSubClassReasoning()));
		m.add(implNode, CONFIG.Shacl.performanceLogging, BooleanLiteral.valueOf(isPerformanceLogging()));
		m.add(implNode, CONFIG.Shacl.serializableValidation, BooleanLiteral.valueOf(isSerializableValidation()));
		m.add(implNode, CONFIG.Shacl.eclipseRdf4jShaclExtensions,
				BooleanLiteral.valueOf(isEclipseRdf4jShaclExtensions()));
		m.add(implNode, CONFIG.Shacl.dashDataShapes, BooleanLiteral.valueOf(isDashDataShapes()));
		m.add(implNode, CONFIG.Shacl.validationResultsLimitTotal, literal(getValidationResultsLimitTotal()));
		m.add(implNode, CONFIG.Shacl.validationResultsLimitPerConstraint,
				literal(getValidationResultsLimitPerConstraint()));
		m.add(implNode, CONFIG.Shacl.transactionalValidationLimit, literal(getTransactionalValidationLimit()));

		for (IRI shapesGraph : shapesGraphs) {
			m.add(implNode, CONFIG.Shacl.shapesGraph, shapesGraph);
		}

		return implNode;
	}

	@Override
	public void parse(Model m, Resource implNode) throws SailConfigException {
		super.parse(m, implNode);

		try {
			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.parallelValidation, PARALLEL_VALIDATION)
					.ifPresent(l -> setParallelValidation(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.logValidationPlans, LOG_VALIDATION_PLANS)
					.ifPresent(l -> setLogValidationPlans(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.logValidationViolations, LOG_VALIDATION_VIOLATIONS)
					.ifPresent(l -> setLogValidationViolations(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.validationEnabled, VALIDATION_ENABLED)
					.ifPresent(l -> setValidationEnabled(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.cacheSelectNodes, CACHE_SELECT_NODES)
					.ifPresent(l -> setCacheSelectNodes(l.booleanValue()));

			Configurations
					.getLiteralValue(m, implNode, CONFIG.Shacl.globalLogValidationExecution,
							GLOBAL_LOG_VALIDATION_EXECUTION)
					.ifPresent(l -> setGlobalLogValidationExecution(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.rdfsSubClassReasoning, RDFS_SUB_CLASS_REASONING)
					.ifPresent(l -> setRdfsSubClassReasoning(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.performanceLogging, PERFORMANCE_LOGGING)
					.ifPresent(l -> setPerformanceLogging(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.serializableValidation, SERIALIZABLE_VALIDATION)
					.ifPresent(l -> setSerializableValidation(l.booleanValue()));

			Configurations
					.getLiteralValue(m, implNode, CONFIG.Shacl.eclipseRdf4jShaclExtensions,
							ShaclSailSchema.ECLIPSE_RDF4J_SHACL_EXTENSIONS)
					.ifPresent(l -> setEclipseRdf4jShaclExtensions(l.booleanValue()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Shacl.dashDataShapes, ShaclSailSchema.DASH_DATA_SHAPES)
					.ifPresent(l -> setDashDataShapes(l.booleanValue()));

			Configurations
					.getLiteralValue(m, implNode, CONFIG.Shacl.validationResultsLimitTotal,
							ShaclSailSchema.VALIDATION_RESULTS_LIMIT_TOTAL)
					.ifPresent(l -> setValidationResultsLimitTotal(l.longValue()));

			Configurations
					.getLiteralValue(m, implNode, CONFIG.Shacl.validationResultsLimitPerConstraint,
							ShaclSailSchema.VALIDATION_RESULTS_LIMIT_PER_CONSTRAINT)
					.ifPresent(l -> setValidationResultsLimitPerConstraint(l.longValue()));

			Configurations
					.getLiteralValue(m, implNode, CONFIG.Shacl.transactionalValidationLimit,
							ShaclSailSchema.TRANSACTIONAL_VALIDATION_LIMIT)
					.ifPresent(l -> setTransactionalValidationLimit(l.longValue()));

			if (m.contains(implNode, CONFIG.Shacl.shapesGraph, null) || m.contains(implNode, SHAPES_GRAPH, null)) {
				setShapesGraphs(
						Configurations
								.getPropertyValues(m, implNode, CONFIG.Shacl.shapesGraph, ShaclSailSchema.SHAPES_GRAPH)
								.stream()
								.peek(v -> {
									if (!v.isIRI()) {
										throw new IllegalArgumentException(
												"Expected IRI but found " + v.getClass().getSimpleName() + "for value "
														+ v.stringValue());
									}
								})
								.map(o -> ((IRI) o))
								.collect(Collectors.toUnmodifiableSet())
				);
			}

		} catch (IllegalArgumentException e) {
			throw new SailConfigException("error parsing Sail configuration", e);
		}

	}

}