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

com.metreeca.rdf4j.assets.GraphStats Maven / Gradle / Ivy

/*
 * Copyright © 2013-2021 Metreeca srl
 *
 * 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.metreeca.rdf4j.assets;

import com.metreeca.json.Shape;
import com.metreeca.json.queries.Stats;
import com.metreeca.rest.assets.Engine;

import org.eclipse.rdf4j.model.*;
import org.eclipse.rdf4j.model.vocabulary.OWL;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.query.AbstractTupleQueryResultHandler;
import org.eclipse.rdf4j.query.BindingSet;

import java.math.BigInteger;
import java.util.*;

import static com.metreeca.json.Values.*;
import static com.metreeca.json.shapes.And.and;
import static com.metreeca.rdf4j.SPARQLScribe.is;
import static com.metreeca.rdf4j.SPARQLScribe.*;
import static com.metreeca.rdf4j.assets.Graph.graph;
import static com.metreeca.rest.Context.asset;
import static com.metreeca.rest.Scribe.indent;
import static com.metreeca.rest.Scribe.text;
import static com.metreeca.rest.Scribe.*;

final class GraphStats extends GraphFacts {

	private final Options options=options();

	private final Graph graph=asset(graph());


	GraphStats(final Options options) {
		super(options);
	}


	Collection process(final IRI resource, final Stats stats) {

		final Shape shape=stats.shape();
		final List path=stats.path();
		final int offset=stats.offset();
		final int limit=stats.limit();

		final Shape filter=shape
				.filter(resource)
				.resolve(resource); // .filter() may introduce focus values › resolve afterwards

		final Shape convey=shape
				.convey()
				.resolve(resource);

		final Shape select=and(filter, path(convey, path)).label(this::label); // requires path to exist in convey

		final String hook=hook(select, path);

		final Collection model=new LinkedHashSet<>();

		final Map counts=new HashMap<>();

		final Collection mins=new ArrayList<>();
		final Collection maxs=new ArrayList<>();

		evaluate(() -> graph.exec(connection -> {
			connection.prepareTupleQuery(compile(() -> code(list(

					comment("stats query"),

					prefix(NS),
					prefix(OWL.NS),
					prefix(RDFS.NS),

					space(select(), where(

							space(block(

									space(select(space(indent(

											var("type"),
											as("min", min(var(hook))),
											as("max", max(var(hook))),
											as("count", count(true, var(hook)))

									)))),

									space(where(

											space(tree(select, true)),

											space(bind("type", is(
													isBlank(var(hook)),
													text(":bnode"),
													is(
															isIRI(var(hook)),
															text(":iri"),
															datatype(var(hook))
													)
											)))

											// !!! sampling w/ options.stats()

									)),

									space(
											line(group(var("type"))),
											line(having(gt(count(true, var(hook)), text(0)))),
											line(order(desc(var("count")), var("type"))),
											line(offset(offset)),
											line(limit(limit))
									)

							)),

							space(
									line(optional(edge(var("type"), "rdfs:label", var("type_label")))),
									line(optional(edge(var("type"), "rdfs:comment", var("type_notes"))))
							),

							space(
									line(optional(edge(var("min"), "rdfs:label", var("min_label")))),
									line(optional(edge(var("min"), "rdfs:comment", var("min_notes"))))
							),

							space(
									line(optional(edge(var("max"), "rdfs:label", var("max_label")))),
									line(optional(edge(var("max"), "rdfs:comment", var("max_notes"))))
							)

					))

			)))).evaluate(new AbstractTupleQueryResultHandler() {

				@Override public void handleSolution(final BindingSet bindings) {

					final Resource type=(Resource)bindings.getValue("type");

					final Value type_label=bindings.getValue("type_label");
					final Value type_notes=bindings.getValue("type_notes");

					final Value min=bindings.getValue("min");
					final Value max=bindings.getValue("max");

					final Value min_label=bindings.getValue("min_label");
					final Value min_notes=bindings.getValue("min_notes");

					final Value max_label=bindings.getValue("max_label");
					final Value max_notes=bindings.getValue("max_notes");

					// ;(virtuoso) counts are returned as xsd:int… cast to stay consistent

					final BigInteger count=integer(bindings.getValue("count")).orElse(BigInteger.ZERO);

					model.add(statement(resource, Engine.stats, type));
					model.add(statement(type, Engine.count, literal(count)));

					if ( type_label != null ) { model.add(statement(type, RDFS.LABEL, type_label)); }
					if ( type_notes != null ) { model.add(statement(type, RDFS.COMMENT, type_notes)); }

					if ( min != null ) { model.add(statement(type, Engine.min, min)); }
					if ( max != null ) { model.add(statement(type, Engine.max, max)); }

					if ( min_label != null ) { model.add(statement((Resource)min, RDFS.LABEL, min_label)); }
					if ( min_notes != null ) { model.add(statement((Resource)min, RDFS.COMMENT, min_notes)); }

					if ( max_label != null ) { model.add(statement((Resource)max, RDFS.LABEL, max_label)); }
					if ( max_notes != null ) { model.add(statement((Resource)max, RDFS.COMMENT, max_notes)); }

					counts.putIfAbsent(type, count);

					if ( min != null ) { mins.add(min); }
					if ( max != null ) { maxs.add(max); }

				}

			});
		}));

		model.add(statement(resource, Engine.count, literal(counts.values().stream()
				.reduce(BigInteger.ZERO, BigInteger::add)
		)));

		mins.stream()
				.reduce((x, y) -> compare(x, y) < 0 ? x : y)
				.ifPresent(min -> model.add(statement(resource, Engine.min, min)));

		maxs.stream()
				.reduce((x, y) -> compare(x, y) > 0 ? x : y)
				.ifPresent(max -> model.add(statement(resource, Engine.max, max)));

		return model;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy