
com.metreeca.rdf4j.services.GraphFacts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metreeca-rdf4j Show documentation
Show all versions of metreeca-rdf4j Show documentation
Connector kit for RDF4J-based SPARQL repositories.
The newest version!
/*
* Copyright © 2013-2022 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.services;
import com.metreeca.json.Shape;
import com.metreeca.json.Values;
import com.metreeca.json.shapes.*;
import com.metreeca.rest.Config;
import com.metreeca.rest.Scribe;
import com.metreeca.rest.services.Logger;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static com.metreeca.json.Values.*;
import static com.metreeca.json.shapes.All.all;
import static com.metreeca.json.shapes.And.and;
import static com.metreeca.json.shapes.Field.field;
import static com.metreeca.json.shapes.Link.link;
import static com.metreeca.json.shapes.Or.or;
import static com.metreeca.json.shapes.When.when;
import static com.metreeca.rdf4j.SPARQLScribe.datatype;
import static com.metreeca.rdf4j.SPARQLScribe.edge;
import static com.metreeca.rdf4j.SPARQLScribe.eq;
import static com.metreeca.rdf4j.SPARQLScribe.filter;
import static com.metreeca.rdf4j.SPARQLScribe.gt;
import static com.metreeca.rdf4j.SPARQLScribe.gte;
import static com.metreeca.rdf4j.SPARQLScribe.in;
import static com.metreeca.rdf4j.SPARQLScribe.isBlank;
import static com.metreeca.rdf4j.SPARQLScribe.isIRI;
import static com.metreeca.rdf4j.SPARQLScribe.isLiteral;
import static com.metreeca.rdf4j.SPARQLScribe.lang;
import static com.metreeca.rdf4j.SPARQLScribe.lt;
import static com.metreeca.rdf4j.SPARQLScribe.lte;
import static com.metreeca.rdf4j.SPARQLScribe.neq;
import static com.metreeca.rdf4j.SPARQLScribe.optional;
import static com.metreeca.rdf4j.SPARQLScribe.or;
import static com.metreeca.rdf4j.SPARQLScribe.regex;
import static com.metreeca.rdf4j.SPARQLScribe.str;
import static com.metreeca.rdf4j.SPARQLScribe.string;
import static com.metreeca.rdf4j.SPARQLScribe.strlen;
import static com.metreeca.rdf4j.SPARQLScribe.strstarts;
import static com.metreeca.rdf4j.SPARQLScribe.union;
import static com.metreeca.rdf4j.SPARQLScribe.values;
import static com.metreeca.rdf4j.SPARQLScribe.var;
import static com.metreeca.rest.Scribe.indent;
import static com.metreeca.rest.Scribe.text;
import static com.metreeca.rest.Scribe.*;
import static com.metreeca.rest.Toolbox.service;
import static com.metreeca.rest.services.Logger.logger;
import static com.metreeca.rest.services.Logger.time;
import static java.lang.String.format;
import static java.lang.String.valueOf;
abstract class GraphFacts {
static final String root="0";
static String alias(final String id) {
return id+'a';
}
static String value(final String id) {
return id+'v';
}
/**
* @param id a template node id
* @param ids the set of known template node ids
*
* @return the id of the principal node proxied by {@code id}
*
* @see "linking @ metreeca-json design notes"
*/
static String principal(final String id, final Collection ids) {
return id.endsWith("a") ? id
: id.endsWith("v") ? id.substring(0, id.length()-1)
: Optional.of(alias(id)).filter(ids::contains).orElse(id);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private final Config config;
private int label=1; // the next label available for tagging (0 reserved for the root node)
private final Logger logger=service(logger());
GraphFacts(final Config config) {
this.config=config;
}
Config config() {
return config;
}
String label() {
return valueOf(label++);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
String compile(final Supplier generator) {
return time(generator).apply((t, v) -> logger
.debug(this, () -> format("executing %s", v))
.debug(this, () -> format("generated in <%,d> ms", t))
);
}
void evaluate(final Runnable task) {
time(task).apply(t -> logger
.debug(this, () -> format("evaluated in <%,d> ms", t))
);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static Scribe tree(final Shape shape, final boolean required) {
return list(
all(shape) // root universal constraints
.map(values -> space(values(var(root), values)))
.map(Scribe::space)
.orElse(nothing()),
space(shape.map(new TreeProbe(root, required)))
);
}
static Shape path(final Shape shape, final List path) {
return Optional.of(shape.map(new PathProbe(path)))
.filter(s -> path.isEmpty() || !s.equals(and()))
.orElseThrow(() ->
new IllegalArgumentException(format("unknown path step %s", Values.format(path.get(0))))
);
}
static String hook(final Shape shape, final List path) {
return hook(root, shape, path);
}
private static String hook(final String anchor, final Shape shape, final List path) {
return Optional.ofNullable(shape.map(new HookProbe(anchor, path)))
.orElseGet(() -> {
if ( !path.isEmpty() ) {
throw new IllegalArgumentException(format("unknown path step %s", Values.format(path.get(0))));
}
return anchor;
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static final class TreeProbe extends Shape.Probe {
private final String anchor;
private final boolean required;
private TreeProbe(final String anchor, final boolean filter) {
this.anchor=anchor;
this.required=filter;
}
@Override public Scribe probe(final Datatype datatype) {
final IRI iri=datatype.iri();
return iri.equals(ValueType) ? nothing() : line(filter(
iri.equals(ResourceType) ? or(isBlank(var(anchor)), isIRI(var(anchor)))
: iri.equals(BNodeType) ? isBlank(var(anchor))
: iri.equals(IRIType) ? isIRI(var(anchor))
: iri.equals(LiteralType) ? isLiteral(var(anchor))
: iri.equals(RDF.LANGSTRING) ? neq(lang(var(anchor)), string(""))
: eq(datatype(var(anchor)), text(iri))
));
}
@Override public Scribe probe(final Clazz clazz) {
return edge(var(anchor), text("a/rdfs:subClassOf*"), text(clazz.iri()));
}
@Override public Scribe probe(final Range range) {
if ( required ) {
return probe((Shape)range); // !!! tbi (filter not exists w/ special root treatment)
} else {
return range.values().isEmpty() ? nothing() : line(filter(
in(var(anchor), range.values().stream().map(Scribe::text))
));
}
}
@Override public Scribe probe(final Lang lang) {
if ( required ) {
return probe((Shape)lang); // !!! tbi (filter not exists w/ special root treatment)
} else {
return lang.tags().isEmpty() ? nothing() : filter(
in(lang(var(anchor)), lang.tags().stream().map(Values::quote).map(Scribe::text))
);
}
}
@Override public Scribe probe(final MinExclusive minExclusive) {
return filter(gt(var(anchor), text(minExclusive.limit())));
}
@Override public Scribe probe(final MaxExclusive maxExclusive) {
return filter(lt(var(anchor), text(maxExclusive.limit())));
}
@Override public Scribe probe(final MinInclusive minInclusive) {
return filter(gte(var(anchor), text(minInclusive.limit())));
}
@Override public Scribe probe(final MaxInclusive maxInclusive) {
return filter(lte(var(anchor), text(maxInclusive.limit())));
}
@Override public Scribe probe(final MinLength minLength) {
return filter(gte(strlen(str(var(anchor))), text(minLength.limit())));
}
@Override public Scribe probe(final MaxLength maxLength) {
return filter(lte(strlen(str(var(anchor))), text(maxLength.limit())));
}
@Override public Scribe probe(final Pattern pattern) {
return filter(regex(
str(var(anchor)), text(quote(pattern.expression())), text(quote(pattern.flags()))
));
}
@Override public Scribe probe(final Like like) {
return filter(regex(
str(var(anchor)), text(quote(like.toExpression()))
));
}
@Override public Scribe probe(final Stem stem) {
return filter(strstarts(
str(var(anchor)), text(quote(stem.prefix()))
));
}
@Override public Scribe probe(final MinCount minCount) {
return required ? probe((Shape)minCount) : nothing();
}
@Override public Scribe probe(final MaxCount maxCount) {
return required ? probe((Shape)maxCount) : nothing();
}
@Override public Scribe probe(final All all) {
return nothing(); // universal constraints handled by skeleton()
}
@Override public Scribe probe(final Any any) {
return space(anchor.equals(root)
? values(var(anchor), any.values())
: filter(in(var(anchor), any.values().stream().map(Scribe::text)))
);
}
@Override public Scribe probe(final Localized localized) {
return required ? probe((Shape)localized) : nothing();
}
@Override public Scribe probe(final Field field) {
final String label=field.label();
final IRI iri=field.iri();
final Shape shape=field.shape();
return shape.map(new Shape.Probe<>() {
@Override public Scribe probe(final Or or) { // push field inside union
return space(union(or.shapes().stream().map(s ->
block(space(field(label, iri, s).map(TreeProbe.this)))
)));
}
@Override protected Scribe probe(final Shape shape) {
return list(
space(edge(var(anchor), text(iri), indent(list(", ", Stream.concat(
Stream.of(var(label)), // filtering/projection hook
all(shape).orElseGet(Collections::emptySet).stream().map(Scribe::text)
))))),
space(shape.map(new TreeProbe(label, required)))
).map(scribe -> required ? scribe : space(optional(scribe)));
}
});
}
@Override public Scribe probe(final Link link) {
final IRI iri=link.iri();
final Shape shape=link.shape();
final String id=direct(iri) ? value(anchor) : alias(anchor);
return list(
space(edge(var(anchor), text(iri), var(id))),
space(shape.map(new TreeProbe(id, required)))
);
}
@Override public Scribe probe(final And and) {
return list(and.shapes().stream().map(shape -> shape.map(this)));
}
@Override public Scribe probe(final Or or) {
return space(union(or.shapes().stream().map(s -> block(space(s.map(this))))));
}
@Override public Scribe probe(final Shape shape) {
throw new UnsupportedOperationException(shape.toString());
}
}
private static final class PathProbe extends Shape.Probe {
private final List path;
PathProbe(final List path) {
this.path=path;
}
@Override public Shape probe(final Field field) {
return path.isEmpty() || !field.iri().equals(path.get(0)) ? and() : field(
field.label(), field.iri(), path(field.shape(), path.subList(1, path.size()))
);
}
@Override public Shape probe(final Link link) {
return link(link.iri(), link.shape().map(this));
}
@Override public Shape probe(final When when) {
return when(when.test(), when.pass().map(this), when.fail().map(this));
}
@Override public Shape probe(final And and) {
return and(and.shapes().stream().map(this));
}
@Override public Shape probe(final Or or) {
return or(or.shapes().stream().map(this));
}
@Override protected Shape probe(final Shape shape) {
return and();
}
}
private static final class HookProbe extends Shape.Probe {
private final String anchor;
private final List path;
HookProbe(final String anchor, final List path) {
this.anchor=anchor;
this.path=path;
}
@Override public String probe(final Field field) {
return path.isEmpty() || !field.iri().equals(path.get(0)) ? null : Optional
.ofNullable(hook(field.label(), field.shape(), path.subList(1, path.size())))
.orElse(field.label());
}
@Override public String probe(final Link link) {
return Optional
.ofNullable(link.shape().map(this))
.orElseGet(() -> direct(link.iri()) ? value(anchor) : alias(anchor));
}
@Override public String probe(final When when) {
throw new UnsupportedOperationException(when.toString()); // make sure we can't reach multiple field hooks
}
@Override public String probe(final And and) {
return probe(and.shapes().stream());
}
@Override public String probe(final Or or) {
return probe(or.shapes().stream());
}
private String probe(final Stream shapes) {
return shapes.map(this).filter(Objects::nonNull).findFirst().orElse(null);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy