blob: ca307398fc34870b80c344fd744db27d6ff4c547 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.commons.rdf.jsonldjava;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.rdf.api.BlankNodeOrIRI;
import org.apache.commons.rdf.api.GraphLike;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.Literal;
import org.apache.commons.rdf.api.RDFTerm;
// NOTE: To avod confusion, don't importing either of the Quad
import org.apache.commons.rdf.api.Triple;
import org.apache.commons.rdf.api.TripleLike;
import com.github.jsonldjava.core.RDFDataset;
import com.github.jsonldjava.core.RDFDataset.Node;
/**
* Common abstract {@link GraphLike}.
* <p>
* Specialised by {@link JsonLdGraph}, {@link JsonLdUnionGraph} and
* {@link JsonLdDataset}.
*
* @param <T>
* specialisation of {@link TripleLike}, e.g. {@link Triple} or
* {@link org.apache.commons.rdf.api.Quad}
*/
public interface JsonLdGraphLike<T extends TripleLike> extends GraphLike<T> {
/**
* Return the underlying JSONLD-Java {@link RDFDataset}.
* <p>
* Changes in the JSONLD-Java dataset is reflected in this class and vice
* versa.
*
* @return The underlying JSONLD-JAva RDFDataset
*/
RDFDataset getRdfDataSet();
}
abstract class AbstractJsonLdGraphLike<T extends TripleLike> implements JsonLdGraphLike<T> {
/**
* Used by {@link #bnodePrefix()} to get a unique UUID per JVM run
*/
private static final UUID SALT = UUID.randomUUID();
/**
* Prefix to use in blank node identifiers
*/
final String bnodePrefix;
final JsonLdRDF factory;
/**
* The underlying JSON-LD {@link RDFDataset}.
* <p>
* Note: This is NOT final as it is reset to <code>null</code> by
* {@link #close()} (to free memory).
*/
RDFDataset rdfDataSet;
AbstractJsonLdGraphLike(final RDFDataset rdfDataSet) {
this(rdfDataSet, "urn:uuid:" + SALT + "#" + "g" + System.identityHashCode(rdfDataSet));
}
AbstractJsonLdGraphLike(final RDFDataset rdfDataSet, final String bnodePrefix) {
this.rdfDataSet = Objects.requireNonNull(rdfDataSet);
this.bnodePrefix = Objects.requireNonNull(bnodePrefix);
this.factory = new JsonLdRDF(bnodePrefix);
}
AbstractJsonLdGraphLike(final String bnodePrefix) {
this(new RDFDataset(), bnodePrefix);
}
@Override
public void add(final T t) {
// add triples to default graph by default
BlankNodeOrIRI graphName = null;
if (t instanceof org.apache.commons.rdf.api.Quad) {
final org.apache.commons.rdf.api.Quad q = (org.apache.commons.rdf.api.Quad) t;
graphName = q.getGraphName().orElse(null);
}
// FIXME: JSON-LD's rdfDataSet.addQuad method does not support
// generalized RDF, so we have to do a naive cast here
add(graphName, (BlankNodeOrIRI) t.getSubject(), (IRI) t.getPredicate(), t.getObject());
}
void add(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
final String g = factory.asJsonLdString(graphName);
final String s = factory.asJsonLdString(subject);
final String p = factory.asJsonLdString(predicate);
if (object instanceof BlankNodeOrIRI) {
final String o = factory.asJsonLdString((BlankNodeOrIRI) object);
rdfDataSet.addQuad(s, p, o, g);
} else if (object instanceof Literal) {
final Literal literal = (Literal) object;
final String language = literal.getLanguageTag().orElse(null);
final String datatype = literal.getDatatype().getIRIString();
rdfDataSet.addQuad(s, p, literal.getLexicalForm(), datatype, language, g);
}
}
public void close() {
// Drop the memory reference, but don't clear it
rdfDataSet = null;
}
@Override
public void clear() {
filteredGraphs(null).forEach(List::clear);
// In theory we could use
// rdfDataSet.clear();
// but then we would need to also do
// rdfDataSet.put("@default", new ArrayList());
// .. both of which seems to be touching too much on JsonLd-Java's
// internal structure
}
@Override
public boolean contains(final T tripleOrQuad) {
return stream().anyMatch(Predicate.isEqual(tripleOrQuad));
}
@Override
public RDFDataset getRdfDataSet() {
return rdfDataSet;
}
@Override
public Stream<? extends T> stream() {
return rdfDataSet.graphNames().parallelStream().map(rdfDataSet::getQuads)
.flatMap(List<RDFDataset.Quad>::parallelStream).map(this::asTripleOrQuad);
}
/**
* Convert JsonLd Quad to a Commons RDF {@link Triple} or
* {@link org.apache.commons.rdf.api.Quad}
*
*
* @see JsonLdRDF#asTriple(Quad)
* @see JsonLdRDF#asQuad(Quad)
* @param jsonldQuad
* jsonld quad to convert
* @return converted {@link TripleLike}
*/
abstract T asTripleOrQuad(RDFDataset.Quad jsonldQuad);
// This will be made public in JsonLdDataset
// and is used by the other methods.
boolean contains(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI s, final IRI p, final RDFTerm o) {
return filteredGraphs(graphName).flatMap(List::stream).anyMatch(quadFilter(s, p, o));
}
Stream<List<RDFDataset.Quad>> filteredGraphs(final Optional<BlankNodeOrIRI> graphName) {
return rdfDataSet.graphNames().parallelStream()
// if graphName == null (wildcard), select all graphs,
// otherwise check its jsonld string
// (including @default for default graph)
.filter(g -> graphName == null || g.equals(graphName.map(factory::asJsonLdString).orElse("@default")))
// remove the quads which match our filter (which could have
// nulls as wildcards)
.map(rdfDataSet::getQuads);
}
String graphNameAsJsonLdString(final T tripleOrQuad) {
if (tripleOrQuad instanceof org.apache.commons.rdf.api.Quad) {
final org.apache.commons.rdf.api.Quad quad = (org.apache.commons.rdf.api.Quad) tripleOrQuad;
return quad.getGraphName().map(factory::asJsonLdString).orElse("@default");
}
return "@default";
}
Predicate<RDFDataset.Quad> quadFilter(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
final Optional<Node> subjectNode = Optional.ofNullable(subject).map(factory::asJsonLdNode);
final Optional<Node> predicateNode = Optional.ofNullable(predicate).map(factory::asJsonLdNode);
final Optional<Node> objectNode = Optional.ofNullable(object).map(factory::asJsonLdNode);
return q -> {
if (subjectNode.isPresent() && subjectNode.get().compareTo(q.getSubject()) != 0) {
return false;
}
if (predicateNode.isPresent() && predicateNode.get().compareTo(q.getPredicate()) != 0) {
return false;
}
if (objectNode.isPresent()) {
if (object instanceof Literal && q.getObject().isLiteral()) {
// Special handling for COMMONSRDF-56, COMMONSRDF-51:
// Less efficient wrapper to a Commons RDF Literal so
// we can use our RDF 1.1-compliant .equals()
final RDFTerm otherObj = factory.asRDFTerm(q.getObject());
if (! (object.equals(otherObj))) {
return false;
}
} else {
// JSONLD-Java's .compareTo can handle IRI, BlankNode and type-mismatch
if (objectNode.get().compareTo(q.getObject()) != 0) {
return false;
}
}
}
// All patterns checked, must be good!
return true;
};
}
// NOTE: This is made public in JsonLdDataset and is used by the other
// remove methods.
void remove(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
// remove the quads which match our filter (which could have nulls as
// wildcards)
filteredGraphs(graphName).forEach(t -> t.removeIf(quadFilter(subject, predicate, object)));
}
}