001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.commons.rdf.jsonldjava; 019 020import java.util.List; 021import java.util.Objects; 022import java.util.Optional; 023import java.util.UUID; 024import java.util.function.Predicate; 025import java.util.stream.Stream; 026 027import org.apache.commons.rdf.api.BlankNodeOrIRI; 028import org.apache.commons.rdf.api.GraphLike; 029import org.apache.commons.rdf.api.IRI; 030import org.apache.commons.rdf.api.Literal; 031import org.apache.commons.rdf.api.RDFTerm; 032// NOTE: To avod confusion, don't importing either of the Quad 033import org.apache.commons.rdf.api.Triple; 034import org.apache.commons.rdf.api.TripleLike; 035 036import com.github.jsonldjava.core.RDFDataset; 037import com.github.jsonldjava.core.RDFDataset.Node; 038 039/** 040 * Common abstract {@link GraphLike}. 041 * <p> 042 * Specialised by {@link JsonLdGraph}, {@link JsonLdUnionGraph} and 043 * {@link JsonLdDataset}. 044 * 045 * @param <T> 046 * specialisation of {@link TripleLike}, e.g. {@link Triple} or 047 * {@link org.apache.commons.rdf.api.Quad} 048 */ 049public interface JsonLdGraphLike<T extends TripleLike> extends GraphLike<T> { 050 /** 051 * Return the underlying JSONLD-Java {@link RDFDataset}. 052 * <p> 053 * Changes in the JSONLD-Java dataset is reflected in this class and vice 054 * versa. 055 * 056 * @return The underlying JSONLD-JAva RDFDataset 057 */ 058 public RDFDataset getRdfDataSet(); 059} 060 061abstract class AbstractJsonLdGraphLike<T extends TripleLike> implements JsonLdGraphLike<T> { 062 063 /** 064 * Used by {@link #bnodePrefix()} to get a unique UUID per JVM run 065 */ 066 private static UUID SALT = UUID.randomUUID(); 067 068 /** 069 * Prefix to use in blank node identifiers 070 */ 071 final String bnodePrefix; 072 073 final JsonLdRDF factory; 074 075 /** 076 * The underlying JSON-LD {@link RDFDataset}. 077 * <p> 078 * Note: This is NOT final as it is reset to <code>null</code> by 079 * {@link #close()} (to free memory). 080 */ 081 RDFDataset rdfDataSet; 082 083 AbstractJsonLdGraphLike(final RDFDataset rdfDataSet) { 084 this(rdfDataSet, "urn:uuid:" + SALT + "#" + "g" + System.identityHashCode(rdfDataSet)); 085 } 086 087 AbstractJsonLdGraphLike(final RDFDataset rdfDataSet, final String bnodePrefix) { 088 this.rdfDataSet = Objects.requireNonNull(rdfDataSet); 089 this.bnodePrefix = Objects.requireNonNull(bnodePrefix); 090 this.factory = new JsonLdRDF(bnodePrefix); 091 } 092 093 AbstractJsonLdGraphLike(final String bnodePrefix) { 094 this(new RDFDataset(), bnodePrefix); 095 } 096 097 @Override 098 public void add(final T t) { 099 // add triples to default graph by default 100 BlankNodeOrIRI graphName = null; 101 if (t instanceof org.apache.commons.rdf.api.Quad) { 102 final org.apache.commons.rdf.api.Quad q = (org.apache.commons.rdf.api.Quad) t; 103 graphName = q.getGraphName().orElse(null); 104 } 105 // FIXME: JSON-LD's rdfDataSet.addQuad method does not support 106 // generalized RDF, so we have to do a naive cast here 107 add(graphName, (BlankNodeOrIRI) t.getSubject(), (IRI) t.getPredicate(), t.getObject()); 108 } 109 110 void add(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) { 111 final String g = factory.asJsonLdString(graphName); 112 final String s = factory.asJsonLdString(subject); 113 final String p = factory.asJsonLdString(predicate); 114 if (object instanceof BlankNodeOrIRI) { 115 final String o = factory.asJsonLdString((BlankNodeOrIRI) object); 116 rdfDataSet.addQuad(s, p, o, g); 117 } else if (object instanceof Literal) { 118 final Literal literal = (Literal) object; 119 final String language = literal.getLanguageTag().orElse(null); 120 final String datatype = literal.getDatatype().getIRIString(); 121 rdfDataSet.addQuad(s, p, literal.getLexicalForm(), datatype, language, g); 122 } 123 } 124 125 public void close() { 126 // Drop the memory reference, but don't clear it 127 rdfDataSet = null; 128 } 129 130 @Override 131 public void clear() { 132 filteredGraphs(null).forEach(s -> s.clear()); 133 // In theory we could use 134 // rdfDataSet.clear(); 135 // but then we would need to also do 136 // rdfDataSet.put("@default", new ArrayList()); 137 // .. both of which seems to be touching too much on JsonLd-Java's 138 // internal structure 139 } 140 141 @Override 142 public boolean contains(final T tripleOrQuad) { 143 return stream().anyMatch(Predicate.isEqual(tripleOrQuad)); 144 } 145 146 @Override 147 public RDFDataset getRdfDataSet() { 148 return rdfDataSet; 149 } 150 151 @Override 152 public Stream<? extends T> stream() { 153 return rdfDataSet.graphNames().parallelStream().map(rdfDataSet::getQuads) 154 .flatMap(List<RDFDataset.Quad>::parallelStream).map(this::asTripleOrQuad); 155 } 156 157 /** 158 * Convert JsonLd Quad to a Commons RDF {@link Triple} or 159 * {@link org.apache.commons.rdf.api.Quad} 160 * 161 * 162 * @see JsonLdRDF#asTriple(Quad) 163 * @see JsonLdRDF#asQuad(Quad) 164 * @param jsonldQuad 165 * jsonld quad to convert 166 * @return converted {@link TripleLike} 167 */ 168 abstract T asTripleOrQuad(RDFDataset.Quad jsonldQuad); 169 170 // This will be made public in JsonLdDataset 171 // and is used by the other methods. 172 boolean contains(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI s, final IRI p, final RDFTerm o) { 173 return filteredGraphs(graphName).flatMap(List::stream).anyMatch(quadFilter(s, p, o)); 174 } 175 176 Stream<List<RDFDataset.Quad>> filteredGraphs(final Optional<BlankNodeOrIRI> graphName) { 177 return rdfDataSet.graphNames().parallelStream() 178 // if graphName == null (wildcard), select all graphs, 179 // otherwise check its jsonld string 180 // (including @default for default graph) 181 .filter(g -> graphName == null || g.equals(graphName.map(factory::asJsonLdString).orElse("@default"))) 182 // remove the quads which match our filter (which could have 183 // nulls as wildcards) 184 .map(rdfDataSet::getQuads); 185 } 186 187 String graphNameAsJsonLdString(final T tripleOrQuad) { 188 if (tripleOrQuad instanceof org.apache.commons.rdf.api.Quad) { 189 final org.apache.commons.rdf.api.Quad quad = (org.apache.commons.rdf.api.Quad) tripleOrQuad; 190 return quad.getGraphName().map(factory::asJsonLdString).orElse("@default"); 191 } 192 return "@default"; 193 } 194 195 Predicate<RDFDataset.Quad> quadFilter(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) { 196 final Optional<Node> subjectNode = Optional.ofNullable(subject).map(factory::asJsonLdNode); 197 final Optional<Node> predicateNode = Optional.ofNullable(predicate).map(factory::asJsonLdNode); 198 final Optional<Node> objectNode = Optional.ofNullable(object).map(factory::asJsonLdNode); 199 200 return q -> { 201 if (subjectNode.isPresent() && subjectNode.get().compareTo(q.getSubject()) != 0) { 202 return false; 203 } 204 if (predicateNode.isPresent() && predicateNode.get().compareTo(q.getPredicate()) != 0) { 205 return false; 206 } 207 if (objectNode.isPresent()) { 208 if (object instanceof Literal && q.getObject().isLiteral()) { 209 // Special handling for COMMONSRDF-56, COMMONSRDF-51: 210 // Less efficient wrapper to a Commons RDF Literal so 211 // we can use our RDF 1.1-compliant .equals() 212 final RDFTerm otherObj = factory.asRDFTerm(q.getObject()); 213 if (! (object.equals(otherObj))) { 214 return false; 215 } 216 } else { 217 // JSONLD-Java's .compareTo can handle IRI, BlankNode and type-mismatch 218 if (objectNode.get().compareTo(q.getObject()) != 0) { 219 return false; 220 } 221 } 222 } 223 // All patterns checked, must be good! 224 return true; 225 }; 226 } 227 228 // NOTE: This is made public in JsonLdDataset and is used by the other 229 // remove methods. 230 void remove(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) { 231 // remove the quads which match our filter (which could have nulls as 232 // wildcards) 233 filteredGraphs(graphName).forEach(t -> t.removeIf(quadFilter(subject, predicate, object))); 234 } 235 236}