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.nio.charset.StandardCharsets; 021import java.util.Objects; 022import java.util.UUID; 023 024import org.apache.commons.rdf.api.BlankNode; 025import org.apache.commons.rdf.api.BlankNodeOrIRI; 026import org.apache.commons.rdf.api.Dataset; 027import org.apache.commons.rdf.api.Graph; 028import org.apache.commons.rdf.api.IRI; 029import org.apache.commons.rdf.api.Literal; 030import org.apache.commons.rdf.api.RDFTerm; 031import org.apache.commons.rdf.api.RDF; 032import org.apache.commons.rdf.api.Triple; 033import org.apache.commons.rdf.simple.Types; 034 035import com.github.jsonldjava.core.RDFDataset; 036import com.github.jsonldjava.core.RDFDataset.Node; 037 038/** 039 * JSON-LD Java RDF implementation. 040 */ 041public final class JsonLdRDF implements RDF { 042 043 final String bnodePrefix; 044 045 public JsonLdRDF() { 046 // An "outside Graph" bnodePrefix 047 this("urn:uuid:" + UUID.randomUUID() + "#b"); 048 } 049 050 JsonLdRDF(final String bnodePrefix) { 051 this.bnodePrefix = Objects.requireNonNull(bnodePrefix); 052 } 053 054 /** 055 * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Dataset}. 056 * <p> 057 * Changes to the Commons RDF {@link Dataset} are reflected in the JsonLd 058 * {@link RDFDataset} and vice versa. 059 * 060 * @see #asGraph(RDFDataset) 061 * @param rdfDataSet 062 * JsonLd {@link RDFDataset} to adapt 063 * @return Adapted {@link Dataset} 064 */ 065 public JsonLdDataset asDataset(final RDFDataset rdfDataSet) { 066 return new JsonLdDatasetImpl(rdfDataSet); 067 } 068 069 /** 070 * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Graph}. 071 * <p> 072 * Only triples in the <em>default graph</em> are included. To retrieve any 073 * other graph, {@link #asDataset(RDFDataset)} together with 074 * {@link Dataset#getGraph(BlankNodeOrIRI)}. 075 * <p> 076 * Changes to the Commons RDF {@link Graph} are reflected in the JsonLd 077 * {@link RDFDataset} and vice versa. 078 * 079 * @see #asDataset(RDFDataset) 080 * @see #asUnionGraph(RDFDataset) 081 * @param rdfDataSet 082 * JsonLd {@link RDFDataset} to adapt 083 * @return Adapted {@link Graph} covering the <em>default graph</em> 084 */ 085 public JsonLdGraph asGraph(final RDFDataset rdfDataSet) { 086 return new JsonLdGraphImpl(rdfDataSet); 087 } 088 089 public Node asJsonLdNode(final RDFTerm term) { 090 if (term instanceof JsonLdBlankNode) { 091 final JsonLdBlankNode jsonLdBlankNode = (JsonLdBlankNode) term; 092 if (jsonLdBlankNode.uniqueReference().startsWith(bnodePrefix)) { 093 // Only return blank nodes 'as is' if they have the same prefix 094 return jsonLdBlankNode.asJsonLdNode(); 095 } 096 } else if (term instanceof JsonLdTerm) { 097 // non-Bnodes can always be return as-is 098 return ((JsonLdTerm) term).asJsonLdNode(); 099 } 100 if (term instanceof IRI) { 101 return new RDFDataset.IRI(((IRI) term).getIRIString()); 102 } 103 if (term instanceof BlankNode) { 104 final String ref = ((BlankNode) term).uniqueReference(); 105 if (ref.startsWith(bnodePrefix)) { 106 // one of our own (but no longer a JsonLdBlankNode), 107 // we can recover the label after our unique prefix 108 return new RDFDataset.BlankNode(ref.replace(bnodePrefix, "")); 109 } 110 // The "foreign" unique reference might not be a valid bnode string, 111 // we'll convert to a UUID 112 final UUID uuid = UUID.nameUUIDFromBytes(ref.getBytes(StandardCharsets.UTF_8)); 113 return new RDFDataset.BlankNode("_:" + uuid); 114 } 115 if (term instanceof Literal) { 116 final Literal literal = (Literal) term; 117 return new RDFDataset.Literal(literal.getLexicalForm(), literal.getDatatype().getIRIString(), 118 literal.getLanguageTag().orElse(null)); 119 } 120 throw new IllegalArgumentException("RDFTerm not instanceof IRI, BlankNode or Literal: " + term); 121 } 122 123 /** 124 * Adapt a Commons RDF {@link org.apache.commons.rdf.api.Quad} as a JsonLd 125 * {@link com.github.jsonldjava.core.RDFDataset.Quad}. 126 * 127 * @param quad 128 * Commons RDF {@link org.apache.commons.rdf.api.Quad} to adapt 129 * @return Adapted JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} 130 */ 131 public RDFDataset.Quad asJsonLdQuad(final org.apache.commons.rdf.api.Quad quad) { 132 final BlankNodeOrIRI g = quad.getGraphName().orElse(null); 133 return createJsonLdQuad(g, quad.getSubject(), quad.getPredicate(), quad.getObject()); 134 } 135 136 /** 137 * Adapt a Commons RDF {@link Triple} as a JsonLd 138 * {@link com.github.jsonldjava.core.RDFDataset.Quad}. 139 * 140 * @param triple 141 * Commons RDF {@link Triple} to adapt 142 * @return Adapted JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} 143 */ 144 public RDFDataset.Quad asJsonLdQuad(final Triple triple) { 145 return createJsonLdQuad(null, triple.getSubject(), triple.getPredicate(), triple.getObject()); 146 } 147 148 /** 149 * Adapt a JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} as a 150 * Commons RDF {@link org.apache.commons.rdf.api.Quad}. 151 * <p> 152 * The underlying JsonLd quad can be retrieved with 153 * {@link JsonLdQuad#asJsonLdQuad()}. 154 * 155 * @param quad 156 * A JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} to 157 * adapt 158 * @return Adapted {@link JsonLdQuad} 159 */ 160 public JsonLdQuad asQuad(final RDFDataset.Quad quad) { 161 return new JsonLdQuadImpl(quad, bnodePrefix); 162 } 163 164 /** 165 * Adapt a JsonLd {@link Node} as a Commons RDF {@link RDFTerm}. 166 * <p> 167 * The underlying node can be retrieved with 168 * {@link JsonLdTerm#asJsonLdNode()}. 169 * 170 * @param node 171 * A JsonLd {@link Node} to adapt 172 * @return Adapted {@link JsonLdTerm} 173 */ 174 public JsonLdTerm asRDFTerm(final Node node) { 175 return asRDFTerm(node, bnodePrefix); 176 } 177 178 /** 179 * Adapt a JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} as a 180 * Commons RDF {@link org.apache.commons.rdf.api.Triple}. 181 * <p> 182 * The underlying JsonLd quad can be retrieved with 183 * {@link JsonLdTriple#asJsonLdQuad()}. 184 * 185 * @param quad 186 * A JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} to 187 * adapt 188 * @return Adapted {@link JsonLdTriple} 189 */ 190 public JsonLdTriple asTriple(final RDFDataset.Quad quad) { 191 return new JsonLdTripleImpl(quad, bnodePrefix); 192 } 193 194 /** 195 * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Graph}. 196 * <p> 197 * The graph can be seen as a <em>union graph</em> as it will contains all 198 * the triples across all the graphs of the underlying {@link RDFDataset}. 199 * <p> 200 * Note that some triple operations on a union graph can be inefficient as 201 * they need to remove any duplicate triples across the graphs. 202 * <p> 203 * Changes to the Commons RDF {@link Graph} are reflected in the JsonLd 204 * {@link RDFDataset} and vice versa. Triples removed from the graph are 205 * removed from <strong>all</strong> graphs, while triples added are added 206 * to the <em>default graph</em>. 207 * 208 * @param rdfDataSet 209 * JsonLd {@link RDFDataset} to adapt 210 * @return Adapted {@link Dataset} 211 */ 212 public JsonLdUnionGraph asUnionGraph(final RDFDataset rdfDataSet) { 213 return new JsonLdUnionGraphImpl(rdfDataSet); 214 } 215 216 @Override 217 public JsonLdBlankNode createBlankNode() { 218 final String id = "_:" + UUID.randomUUID().toString(); 219 return new JsonLdBlankNodeImpl(new RDFDataset.BlankNode(id), bnodePrefix); 220 } 221 222 @Override 223 public JsonLdBlankNode createBlankNode(final String name) { 224 final String id = "_:" + name; 225 // TODO: Check if name is valid JSON-LD BlankNode identifier 226 return new JsonLdBlankNodeImpl(new RDFDataset.BlankNode(id), bnodePrefix); 227 } 228 229 @Override 230 public JsonLdDataset createDataset() { 231 return new JsonLdDatasetImpl(bnodePrefix); 232 } 233 234 @Override 235 public JsonLdGraph createGraph() { 236 return new JsonLdGraphImpl(bnodePrefix); 237 } 238 239 @Override 240 public JsonLdIRI createIRI(final String iri) { 241 return new JsonLdIRIImpl(iri); 242 } 243 244 @Override 245 public JsonLdLiteral createLiteral(final String literal) { 246 return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, null, null)); 247 } 248 249 @Override 250 public JsonLdLiteral createLiteral(final String literal, final IRI dataType) { 251 return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, dataType.getIRIString(), null)); 252 } 253 254 @Override 255 public JsonLdLiteral createLiteral(final String literal, final String language) { 256 return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, Types.RDF_LANGSTRING.getIRIString(), language)); 257 } 258 259 @Override 260 public JsonLdQuad createQuad(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) 261 throws IllegalArgumentException, UnsupportedOperationException { 262 return new JsonLdQuadImpl(createJsonLdQuad(graphName, subject, predicate, object), bnodePrefix); 263 } 264 265 @Override 266 public JsonLdTriple createTriple(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) { 267 return new JsonLdTripleImpl(createJsonLdQuad(null, subject, predicate, object), bnodePrefix); 268 } 269 270 String asJsonLdString(final BlankNodeOrIRI blankNodeOrIRI) { 271 if (blankNodeOrIRI == null) { 272 return null; 273 } 274 if (blankNodeOrIRI instanceof IRI) { 275 return ((IRI) blankNodeOrIRI).getIRIString(); 276 } else if (blankNodeOrIRI instanceof BlankNode) { 277 final BlankNode blankNode = (BlankNode) blankNodeOrIRI; 278 final String ref = blankNode.uniqueReference(); 279 if (ref.startsWith(bnodePrefix)) { 280 // One of ours (but possibly not a JsonLdBlankNode) - 281 // we can use the suffix directly 282 return ref.replace(bnodePrefix, ""); 283 } 284 // Map to unique bnode identifier, e.g. 285 // _:0dbd92ee-ab1a-45e7-bba2-7ade54f87ec5 286 final UUID uuid = UUID.nameUUIDFromBytes(ref.getBytes(StandardCharsets.UTF_8)); 287 return "_:" + uuid; 288 } else { 289 throw new IllegalArgumentException("Expected a BlankNode or IRI, not: " + blankNodeOrIRI); 290 } 291 } 292 293 JsonLdTerm asRDFTerm(final Node node, final String blankNodePrefix) { 294 if (node == null) { 295 return null; // e.g. default graph 296 } 297 if (node.isIRI()) { 298 return new JsonLdIRIImpl(node); 299 } else if (node.isBlankNode()) { 300 return new JsonLdBlankNodeImpl(node, blankNodePrefix); 301 } else if (node.isLiteral()) { 302 // TODO: Our own JsonLdLiteral 303 if (node.getLanguage() != null) { 304 return createLiteral(node.getValue(), node.getLanguage()); 305 } 306 return createLiteral(node.getValue(), createIRI(node.getDatatype())); 307 } else { 308 throw new IllegalArgumentException("Node is neither IRI, BlankNode nor Literal: " + node); 309 } 310 } 311 312 RDFDataset.Quad createJsonLdQuad(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) { 313 return new RDFDataset.Quad(asJsonLdNode(subject), asJsonLdNode(predicate), asJsonLdNode(object), 314 asJsonLdString(graphName)); 315 } 316 317}