ShaclSailValidationReportHelper.java

/*******************************************************************************
 * Copyright (c) 2022 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.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Optional;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.common.exception.ValidationException;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.WriterConfig;
import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;

/**
 * @author Florian Kleedorfer
 * @since 4.0.0
 */
@InternalUseOnly
public class ShaclSailValidationReportHelper {
	private static final WriterConfig WRITER_CONFIG = new WriterConfig();

	static {
		WRITER_CONFIG
				.set(BasicWriterSettings.PRETTY_PRINT, true)
				.set(BasicWriterSettings.INLINE_BLANK_NODES, true);
	}

	/**
	 * Finds a validation report using {@link #getValidationReport(Throwable)} and returns a {@link String} containing
	 * the pretty-printed report.
	 *
	 * @param t the {@link Throwable} to start searching for a validation report at
	 * @return an Optional with the pretty-printed report if one is found, empty otherwise.
	 */
	public static Optional<String> getValidationReportAsString(Throwable t) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		printValidationReport(t, baos);
		String reportAsString = baos.toString();
		if (reportAsString == null || reportAsString.isBlank()) {
			return Optional.empty();
		}
		return Optional.of(reportAsString);
	}

	/**
	 * Finds a validation report using {@link #getValidationReport(Throwable)} and pretty-prints it to the specified
	 * output stream.
	 *
	 * @param t   the {@link Throwable} to start searching for a validation report at
	 * @param out the output stream to print to
	 */
	public static void printValidationReport(Throwable t, OutputStream out) {
		Optional<Model> reportOpt = getValidationReport(t);
		if (reportOpt.isPresent()) {
			Rio.write(reportOpt.get(), out, RDFFormat.TURTLE, WRITER_CONFIG);
		}
	}

	/**
	 * Looks for a {@link ValidationException} starting with the specified throwable and working back through the cause
	 * references, and returns the validation report as a {@link Model} if one is found.
	 *
	 * @param t the {@link Throwable} to start the search at
	 * @return an optional with the validation report, or empty.
	 */
	public static Optional<Model> getValidationReport(Throwable t) {
		Throwable throwable;
		for (throwable = t; throwable != null; throwable = throwable.getCause()) {
			if (throwable instanceof ValidationException) {
				return Optional.ofNullable(((ValidationException) throwable).validationReportAsModel());
			}
		}
		return Optional.empty();
	}

}