OperationUtils.java

/*******************************************************************************
 * Copyright (c) 2021 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.spring.dao.support.operation;

import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.Operation;
import org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf;
import org.eclipse.rdf4j.spring.dao.exception.IncorrectResultSetSizeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Florian Kleedorfer
 * @since 4.0.0
 */
public class OperationUtils {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	/**
	 * Returns the object in the {@link Collection} wrapped in an {@link Optional}, an empty Optional if the collection
	 * is empty, throwing an Exception if the Collection contains more than one element.
	 */
	public static <T> Optional<T> atMostOne(Collection<T> objects) {
		int size = objects.size();
		if (size > 1) {
			throw new IncorrectResultSetSizeException(
					"Expected to find at most one entity, but found " + size, 1, size);
		}
		return objects.stream().findFirst();
	}

	/**
	 * Returns the object contained in the specified {@link Optional}, throwing an Exception if it is empty.
	 */
	public static <T> T require(Optional<T> required) {
		if (required.isEmpty()) {
			throw new IncorrectResultSetSizeException(
					"Expected to find exactly one entity but found 0", 1, 0);
		}
		return required.get();
	}

	/**
	 * Returns the element in the {@link Collection}, throwing an exception if the collection is empty or contains more
	 * than one element.
	 */
	public static <T> T exactlyOne(Collection<T> objects) {
		return require(atMostOne(objects));
	}

	/**
	 * Returns the element in the {@link java.util.stream.Stream}, throwing an exception if the stream is empty or
	 * contains more than one element.
	 */
	public static <T> Collector<T, ?, T> toSingleton() {
		return Collectors.collectingAndThen(
				Collectors.toList(),
				list -> {
					int size = list.size();
					if (size != 1) {
						throw new IncorrectResultSetSizeException(
								"Expected exactly one result, found " + size, 1, size);
					}
					return list.get(0);
				});
	}

	/**
	 * Returns the element in the {@link java.util.stream.Stream}, or null if the stream is empty, throwing an exception
	 * if the stream contains more than one element.
	 */
	public static <T> Collector<T, ?, T> toSingletonMaybe() {
		return Collectors.collectingAndThen(
				Collectors.toList(),
				list -> {
					int size = list.size();
					if (size > 1) {
						throw new IncorrectResultSetSizeException(
								"Expected zero or one result, found " + size, 1, size);
					} else if (size == 0) {
						return null;
					}
					return list.get(0);
				});
	}

	/**
	 * Returns the element in the {@link java.util.stream.Stream} wrapped in an {@link Optional} throwing an exception
	 * if the stream contains more than one element.
	 */
	public static <T> Collector<T, ?, Optional<T>> toSingletonOptional() {
		return Collectors.collectingAndThen(
				Collectors.toList(),
				list -> {
					int size = list.size();
					if (size > 1) {
						throw new IncorrectResultSetSizeException(
								"Expected zero or one result, found " + size, 1, 0);
					} else if (size == 0) {
						return Optional.empty();
					}
					return Optional.ofNullable(list.get(0));
				});
	}

	public static void setBindings(Operation operation, Map<String, Value> bindings) {
		debugLogBindings(bindings);
		operation.clearBindings();
		if (bindings != null) {
			bindings.entrySet()
					.stream()
					.forEach(entry -> operation.setBinding(entry.getKey(), entry.getValue()));
		}
	}

	private static void debugLogBindings(Map<String, Value> bindings) {
		if (logger.isDebugEnabled() && bindings != null) {
			logger.debug("bindings: {}", bindings);
			List<String> keys = bindings.keySet().stream().collect(Collectors.toList());
			logger.debug(
					"values block:\n\nVALUES ( {} ) { ( {} ) }\n",
					keys.stream().map(k -> "?" + k).collect(Collectors.joining(" ")),
					keys.stream()
							.map(k -> Rdf.object(bindings.get(k)).getQueryString())
							.collect(Collectors.joining(" ")));
		}
	}
}