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}