User GuideThis page shows some examples of a client using the Commons RDF API. It was last updated for version 0.5.0 of the Commons RDF API.
IntroductionCommons RDF is an API that intends to directly describe RDF 1.1 concepts as a set of corresponding interfaces and methods. RDF conceptsRDF is a graph-based data model, where a graph contains a series of triples, each containing the node-arc-node link subject -> predicate -> object. Nodes in the graph are represented either as IRIs, literals and blank nodes. : This user guide does not intend to give a detailed description of RDF as a data model. To fully understand this user guide, you should have a brief understanding of the core RDF concepts mentioned above. For more information on RDF, see the RDF primer and the RDF concepts specification from W3C. Using Commons RDF from MavenTo use Commons RDF API from an Apache Maven project, add the following dependency to your pom.xml: <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-rdf-api</artifactId> <version>0.5.0</version> </dependency> </dependencies> The <version> above might not be up to date, see the download page for the latest version. If you are testing a SNAPSHOT version, then you will have to either build Commons RDF from source, or add this snapshot repository: <repositories> <repository> <id>apache.snapshots</id> <name>Apache Snapshot Repository</name> <url>http://repository.apache.org/snapshots</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> As Commons RDF requires Java 8 or later, you will also need: <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> In the examples below we will use the simple implementation, but the examples should be equally applicable to other implementations. To add a dependency for the simple implementation, add to your <dependencies>: <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-rdf-simple</artifactId> <version>0.5.0</version> </dependency> The <version> above might not be up to date, see the download page for the latest version. Creating Commons RDF instancesTo create instances of Commons RDF interfaces like Graph and IRI you will need a RDF implementation. Finding an RDF implementationThe implementations of RDF can usually be created using a normal Java constructor, for instance the simple implementation from SimpleRDF: import org.apache.commons.rdf.api.*; import org.apache.commons.rdf.simple.SimpleRDF; // ... RDF rdf = new SimpleRDF(); If you don’t want to depend on instantiating a concrete RDF implementation, you can alternatively use the ServiceLoader to lookup any RDF implementations found on the classpath: import java.util.Iterator; import java.util.ServiceLoader; import org.apache.commons.rdf.api.*; // ... ServiceLoader<RDF> loader = ServiceLoader.load(RDF.class); Iterator<RDF> iterator = loader.iterator(); RDF rdf = iterator.next(); Note that the ServiceLoader approach above might not work well within split classloader systems like OSGi. When using the factory method createBlankNode(String) from different sources, care should be taken to create correspondingly different RDF instances. Using an RDF implementationUsing the RDF implementation you can construct any RDFTerm, e.g. to create a BlankNode, IRI and Literal: BlankNode aliceBlankNode = rdf.createBlankNode(); IRI nameIri = rdf.createIRI("http://example.com/name"); Literal aliceLiteral = rdf.createLiteral("Alice"); You can also create a stand-alone Triple: Triple triple = rdf.createTriple(aliceBlankNode, nameIri, aliceLiteral); The RDF interface also contains more specific variants of some of the methods above, e.g. to create a typed literal. In addition, the implementations of RDF may add specific converter methods and implementation-specific subtypes for interoperability with their underlying RDF framework’s API. RDF termsRDFTerm is the super-interface for instances that can be used as subject, predicate and object of a Triple. The RDF term interfaces are arranged in this type hierarchy: N-Triples stringAll of the RDFTerm types support the ntriplesString() method: System.out.println(aliceBlankNode.ntriplesString()); System.out.println(nameIri.ntriplesString()); System.out.println(aliceLiteral.ntriplesString());
This returns the N-Triples canonical form of the term, which can be useful for debugging or simple serialization. Note: The .toString() of the simple implementation used in some of these examples use ntriplesString() internally, but Commons RDF places no such formal requirement on the .toString() method. Clients that rely on a canonical N-Triples-compatible string should instead use ntriplesString(). IRIAn IRI is a representation of an Internationalized Resource Identifier, e.g. http://example.com/alice or http://ns.example.org/vocab#term.
An IRI identifies a resource that can be used as a subject, predicate or object of a Triple or Quad, where it can also be used a graph name. To create an IRI instance from an RDF implementation, use createIRI: IRI iri = rdf.createIRI("http://example.com/alice"); You can retrieve the IRI string using getIRIString: System.out.println(iri.getIRIString());
Note: The IRI string might contain non-ASCII characters which must be %-encoded for applications that expect an URI. It is currently out of scope for Commons RDF to perform such a conversion, however implementations might provide separate methods for that purpose. Two IRI instances can be compared using the equals method, which uses simple string comparison: IRI iri2 = rdf.createIRI("http://example.com/alice"); System.out.println(iri.equals(iri2));
IRI iri3 = rdf.createIRI("http://example.com/alice/./"); System.out.println(iri.equals(iri3));
Note that IRIs are never equal to objects which are not themselves instances of IRI: System.out.println(iri.equals("http://example.com/alice")); System.out.println(iri.equals(rdf.createLiteral("http://example.com/alice")));
Blank nodeA blank node is a resource which, unlike an IRI, is not directly identified. Blank nodes can be used as subject or object of a Triple or Quad, where it can also be used a graph name. To create a new BlankNode instance from a RDF implementation, use createBlankNode: BlankNode bnode = rdf.createBlankNode(); Every call to createBlankNode() returns a brand new blank node which can be used in multiple triples in multiple graphs. Thus every such blank node can only be equal to itself: System.out.println(bnode.equals(bnode)); System.out.println(bnode.equals(rdf.createBlankNode()));
Sometimes it can be beneficial to create a blank node based on a localized name, without needing to keep object references to earlier BlankNode instances. For that purpose, the RDF interface provides the expanded createBlankNode method: BlankNode b1 = rdf.createBlankNode("b1"); Note that there is no requirement for the ntriplesString() of the BlankNode to reflect the provided name: System.out.println(b1.ntriplesString());
Any later createBlankNode("b1") on the same RDF instance returns a BlankNode which are equal to the previous b1: System.out.println(b1.equals(rdf.createBlankNode("b1")));
That means that care should be taken to create a new RDF instance if making “different” blank nodes (e.g. parsed from a different RDF file) which accidfentally might have the same name: System.out.println(b1.equals(new SimpleRDF().createBlankNode("b1")));
Blank node referenceWhile blank nodes are distinct from IRIs, and don’t have inherent universal identifiers, it can nevertheless be useful for debugging and testing to have a unique reference string for a particular blank node. For that purpose, BlankNode exposes the uniqueReference method: System.out.println(bnode.uniqueReference());
While this reference string might for the simple implementation also be seen within the BlankNode.ntriplesString() result, there is no such guarantee from the Commons RDF API. Clients who need a globally unique reference for a blank node should therefore use the uniqueReference() method. Note: While it is recommended for this string to be (or contain) a UUID string, implementations are free to use any scheme to ensure their blank node references are globally unique. Therefore no assumptions should be made about this string except that it is unique per blank node. LiteralA literal in RDF is a value such as a string, number or a date. A Literal can only be used as an object of a Triple or Quad To create a Literal instance from an RDF implementation, use createLiteral: Literal literal = rdf.createLiteral("Hello world!"); System.out.println(literal.ntriplesString());
The lexical value (what is inside the quotes) can be retrieved using getLexicalForm(): String lexical = literal.getLexicalForm(); System.out.println(lexical);
DatatypeAll literals in RDF 1.1 have a datatype IRI, which can be retrieved using Literal.getDatatype(): IRI datatype = literal.getDatatype(); System.out.println(datatype.ntriplesString());
In RDF 1.1, a simple literal (as created above) always have the type http://www.w3.org/2001/XMLSchema#string (or xsd:string for short). Note: RDF 1.0 had the datatype http://www.w3.org/1999/02/22-rdf-syntax-ns#PlainLiteral to indicate plain literals (untyped), which were distinct from http://www.w3.org/2001/XMLSchema#string (typed). Commons RDF assumes RDF 1.1, which merges the two concepts as the second type, however particular implementations might have explicit options for RDF 1.0 support, in which case you might find Literal instances with the deprecated plain literal data type. To create a literal with any other datatype (e.g. xsd:double), then create the datatype IRI and pass it to the expanded createLiteral: IRI xsdDouble = rdf.createIRI("http://www.w3.org/2001/XMLSchema#double"); Literal literalDouble = rdf.createLiteral("13.37", xsdDouble); System.out.println(literalDouble.ntriplesString());
TypesThe class Types, which is part of the simple implementation, provides IRI constants for the standard XML Schema datatypes like xsd:dateTime and xsd:float. Using Types, the above example can be simplified to: Literal literalDouble2 = rdf.createLiteral("13.37", Types.XSD_DOUBLE); As the constants in Types are all instances of IRI, so they can also be used for comparisons: System.out.println(Types.XSD_STRING.equals(literal.getDatatype()));
LanguageLiterals may be created with an associated language tag using the expanded createLiteral: Literal inSpanish = rdf.createLiteral("¡Hola, Mundo!", "es"); System.out.println(inSpanish.ntriplesString()); System.out.println(inSpanish.getLexicalForm());
A literal with a language tag always have the implied type http://www.w3.org/1999/02/22-rdf-syntax-ns#langString: System.out.println(inSpanish.getDatatype().ntriplesString());
The language tag can be retrieved using getLanguageTag(): Optional<String> tag = inSpanish.getLanguageTag(); if (tag.isPresent()) { System.out.println(tag.get()); }
The language tag is behind an Optional as it won’t be present for any other datatypes than http://www.w3.org/1999/02/22-rdf-syntax-ns#langString: System.out.println(literal.getLanguageTag().isPresent()); System.out.println(literalDouble.getLanguageTag().isPresent());
TripleA triple in RDF 1.1 consists of:
To construct a Triple from an RDF implementation, use createTriple: BlankNodeOrIRI subject = rdf.createBlankNode(); IRI predicate = rdf.createIRI("http://example.com/says"); RDFTerm object = rdf.createLiteral("Hello"); Triple triple = rdf.createTriple(subject, predicate, object); The subject of the triple can be retrieved using getSubject: BlankNodeOrIRI subj = triple.getSubject(); System.out.println(subj.ntriplesString());
Likewise the predicate using getPredicate: IRI pred = triple.getPredicate(); System.out.println(pred.getIRIString());
Finally, the object of the triple is returned with getObject: RDFTerm obj = triple.getObject(); System.out.println(obj.ntriplesString());
For the subject and object you might find it useful to do Java type checking and casting from the types BlankNodeOrIRI and RDFTerm: if (subj instanceof IRI) { String s = ((IRI) subj).getIRIString(); System.out.println(s); } // .. if (obj instanceof Literal) { IRI type = ((Literal) obj).getDatatype(); System.out.println(type); } In Commons RDF, BlankNodeOrIRI instances are always one of BlankNode or IRI, and RDFTerm instances one of BlankNode, IRI or Literal. A Triple is considered equal to another Triple if each of their subject, predicate and object are equal: System.out.println(triple.equals(rdf.createTriple(subj, pred, obj)));
This equality is true even across implementations, as Commons RDF has specified equality semantics for Triples, Quads, IRIs, Literals and even BlankNodes. QuadA quad is a triple with an associated graph name, and can be a statement in a dataset. Commons RDF represents such statements using the class Quad, which consists of:
To create a Quad, use createQuad: BlankNodeOrIRI graph = rdf.createIRI("http://example.com/graph"); BlankNodeOrIRI subject = rdf.createBlankNode(); IRI predicate = rdf.createIRI("http://example.com/says"); RDFTerm object = rdf.createLiteral("Hello"); Quad quad = rdf.createQuad(graph, subject, predicate, object); The subject, predicate and object are accessible just like in a Triple: IRI pred = quad.getPredicate(); System.out.println(pred.ntriplesString());
Graph nameThe quad’s graph name is accessible using getGraphName(): Optional<BlankNodeOrIRI> g = quad.getGraphName(); The graph name is represented as an Optional, where Optional.empty() indicates that the quad is in the default graph, while if the Optional.isPresent() then the graph name BlankNodeOrIRI is accessible with g.get(): if (g.isPresent()) { BlankNodeOrIRI graphName = g.get(); System.out.println(graphName.ntriplesString()); }
To create a quad in the default graph, supply null as the graph name to the factory method: Quad otherQuad = rdf.createQuad(null, subject, predicate, object); System.out.println(otherQuad.getGraphName().isPresent());
Note that a Quad will never return null on any of its getters, which is why the graph name is wrapped as an Optional. This also allows the use of Java 8 functional programming patterns like: String str = quad.map(BlankNodeOrIRI::ntriplesString).orElse(""); As the createQuad method does not expect an Optional, you might use this orElse pattern to represent the default graph as null: BlankNodeOrIRI g = quad.getGraphName().orElse(null); if (g == null) { System.out.println("In default graph"); } rdf.createQuad(g,s,p,o); Care should be taken with regards when accessing graph named with BlankNodes, as the graph name will be compared using BlankNode’s equality semantics. Quad equalityA Quad is considered equal to another Quad if each of the graph name, subject, predicate and object are equal: System.out.println(quad.equals(otherQuad));
Converting quads to triplesAll quads can be viewed as triples - in a way “stripping” the graph name: Triple quadAsTriple = quad.asTriple(); This can be utilized to compare quads at triple-level (considering just s/p/o): System.out.println(quadAsTriple.equals(otherQuad.asTriple());
To create a triple from a quad, you will need to use RDF.createQuad providing the desired graph name: Triple t; // .. BlankNodeOrIRI g; // .. Quad q = rdf.createQuad(g, t.getSubject(), t.getPredicate(), t.getObject()); TripleLikeNote that the class Quad does not extend the class Triple, as they have different equality semantics. Both Triple and Quad do however share a common “duck-typing” interface TripleLike: TripleLike a = quad; TripleLike b = quad.asTriple(); Unlike Triple and Quad, TripleLike does not mandate any specific .equals(), it just provides common access to getSubject() getPredicate() and getObject(). RDFTerm s = a.getSubject(); RDFTerm p = a.getPredicate(); RDFTerm o = a.getObject(); TripleLike can also be used for generalized RDF therefore all of its parts are returned as RDFTerm. For generalized quads the QuadLike interface extends TripleLike to add getGraphName() as an Optional<T extends RDFTerm>. GraphA graph is a collection of triples. To create a Graph instance from a RDF implementation, use createGraph(): Graph graph = rdf.createGraph(); Implementations will typically also have other ways of retrieving a Graph, e.g. by parsing a Turtle file or connecting to a storage backend. Adding triplesAny Triple can be added to the graph using the add method: graph.add(triple); As an alternative to creating the Triple first, you can use the expanded subject/predicate/object form of Graph.add: IRI bob = rdf.createIRI("http://example.com/bob"); IRI nameIri = rdf.createIRI("http://example.com/name"); Literal bobName = rdf.createLiteral("Bob"); graph.add(bob, nameIRI, bobName); It is not necessary to check if a triple already exist in the graph, as the underlying implementation will ignore duplicate triples. Finding triplesYou can check if the graph contains a triple: System.out.println(graph.contains(triple));
The expanded subject/predicate/object call for Graph.contains() can be used without needing to create a Triple first, and also allow null as a wildcard parameter: System.out.println(graph.contains(null, nameIri, bobName));
Iterating over triplesThe iterate method can be used to sequentially iterate over all the triples of the graph: for (Triple t : graph.iterate()) { System.out.println(t.getObject()); }
The expanded iterate method takes a subject/predicate/object filter which permits the null wildcard: for (Triple t : graph.iterate(null, null, bobName)) { System.out.println(t.getPredicate()); }
Stream of triplesFor processing of larger graphs, and to access more detailed filtering and processing, the stream method return a Java 8 Stream. Some of the implementations (e.g. RDF4J) might require resources to be closed after the stream has been processed, so .stream() should be used within a try-with-resources block. try (Stream<? extends Triple> triples = graph.stream()) { Stream<RDFTerm> subjects = triples.map(t -> t.getObject()); String s = subjects.map(RDFTerm::ntriplesString).collect(Collectors.joining(" ")); System.out.println(s); }
For details about what can be done with a stream, see the java.util.stream documentation. Note that by default the stream will be parallel, use .sequential() if your stream operations need to interact with objects that are not thread-safe. Streams allow advanced filter predicates, but you may find that simple subject/predicate/object patterns are handled more efficiently by the implementation when using the expanded stream method. These can of course be combined: try (Stream<? extends Triple> named = graph.stream(null, nameIri, null)) { Stream<? extends Triple> namedB = named.filter( t -> t.getObject().ntriplesString().contains("B")); System.out.println(namedB.map(t -> t.getSubject()).findAny().get()); }
Removing triplesTriples can be removed from a graph: graph.remove(triple); System.out.println(graph.contains(triple));
The expanded subject/predicate/object form of remove() can be used without needing to construct a Triple first. It also allow null as a wildcard pattern: graph.remove(null, nameIri, null); To remove all triples, use clear: graph.clear(); System.out.println(graph.contains(null, null, null));
DatasetA dataset is a collection of quads, or if you like, a collection of Graphs. To create a Dataset instance from a RDF implementation, use createDataset(): Dataset dataset = rdf.createDataset(); Implementations will typically also have other ways of retrieving a Dataset, e.g. by parsing a JSON-LD file or connecting to a storage backend. Dataset operationsDataset operations match their equivalent operations on Graph, except that methods like add(q) and remove(q) use Quad instead of Triple. dataset.add(quad); System.out.println(dataset.contains(quad)); dataset.remove(quad);
The convenience method add(g,s,p,o) take an additional BlankNodeOrIRI parameter for the graph name - matching RDF.createQuad(g,s,p,o). Note that the expanded pattern methods like contains(g,s,p,o) and stream(g,s,p,o) uses null as a wildcard pattern, and therefore an explicit graph name parameter must be supplied as Optional.empty() (default graph) or wrapped using Optional.of(g): Literal foo = rdf.createLiteral("Foo"); // Match Foo in any graph, any subject, any predicate if (dataset.contains(null, null, null, foo)) { System.out.println("Foo literal found"); } // Match Foo in default graph, any subject, any predicate if (dataset.contains(Optional.empty(), null, null, foo)) { System.out.println("Foo literal found in default graph"); } BlankNodeOrIRI g1 = rdf.createIRI("http://example.com/graph1"); // Match Foo in named graph, any subject, any predicate if (dataset.contains(Optional.of(g1), null, null, foo)) { System.out.println("Foo literal found in default graph"); } Graphs in the datasetAn RDF Dataset is defined as:
It is possible to retrieve these graphs from a Dataset using:
Graph defaultGraph = dataset.getGraph(); BlankNodeOrIRI graphName = rdf.createIRI("http://example.com/graph"); Optional<Graph> otherGraph = dataset.getGraph(graphName); These provide a Graph view of the corresponding Triples in the Dataset: System.out.println(defaultGraph.contains(otherQuad.asTriple())); System.out.println(defaultGraph.size());
It is unspecified if modifications to the returned Graph are reflected in the Dataset. Note that it is unspecified if requesting an unknown graph name will return Optional.empty() or create a new (empty) Graph. Some implementations may also support a union graph, a Graph that contains all triples regardless of their graph names. simple provides DatasetGraphView which can be used with any Dataset for this purpose. Mutability and thread safetyNote: This section is subject to change - see discussion on COMMONSRDF-7 In Commons RDF, all instances of Triple and RDFTerm (e.g. IRI, BlankNode, Literal) are considered immutable. That is, their content does not change, and so calling a method like IRI.getIRIString or Literal.getDatatype will have a return value which .equals() any earlier return values. Being immutable, the Triple and RDFTerm types should be considered thread-safe. Similarly their hashCode() should be considered stable, so any RDFTerm or Triple can be used in hashing collections like HashMap. A Graph may be mutable, particular if it supports methods like Graph.add and Graph.remove. That means that responses to methods like size and contains might change during its lifetime. A mutable Graph might also be modified by operations outside Commons RDF, e.g. because it is backed by a shared datastore with multiple clients. Implementations of Commons RDF may specify the (im)mutability of Graph in further details in their documentation. If a graph is immutable, the methods add and remove may throw a UnsupportedOperationException. Commons RDF does not specify if methods on a Graph are thread-safe. Iterator methods like iterate and stream might throw a ConcurrentModificationException if it detects a thread concurrency modification, although this behaviour is not guaranteed. Implementations of Commons RDF may specify more specific thread-safety considerations. If an implementation does not specify any thread-safety support, then all potentially concurrent access to a Graph must be synchronized, e.g.: Graph graph; // ... synchronized(graph) { graph.add(triple); } // ... synchronized(graph) { for (Triple t : graph) { // ... } } ImplementationsThe Commons RDF API is a set of Java interfaces, which can be implemented by several Java RDF frameworks. See the implementations page for an updated list of providers. Commons RDF defines a RDF interface as a factory for using a particular implementations’ RDFTerm, Triple, Quad, Graph and Dataset. The RDF implementations also add adapter/converter methods to facilitate interoperability with their underlying framework’s API. Note that some RDF frameworks have several possibilities for creating a backend for a Graph or Dataset, which configuration is implementation-specific. Cross-compatibilityWhile different frameworks have their own classes implementing the Commons RDF interfaces, Commons RDF objects are cross-compatible. Thus a client is able to mix and match objects from multiple implementations: import org.apache.commons.rdf.rdf4j.RDF4J; import org.apache.commons.rdf.jena.JenaRDF; RDF rdf4j = new RDF4J(); JenaRDF jena = new JenaRDF(); JenaGraph jenaGraph = jena.createGraph(); // Jena-specific load method jenaGraph.asJenaModel().read("dataset.ttl"); // Another Graph, from a different implementation Graph rdf4jGraph = rdf4j.createGraph(); // Any RDF implementation can make RDFTerms IRI rdfIRI = rdf4j.createIRI("http://example.com/property1"); // and used added to a different implementation's jenaGraph.add(rdfIRI,rdfIRI,rdfIRI); // Both Triple and RDFTerm instances can be used // with interoperability for (Triple t1: g1.stream(null, iri1, null)) { if (g2.contains(t1.getSubject(), null, t1.getObject())) { g2.remove(t1); } } It is however generally recommended to use the matching RDF implementation for operations on a Graph or Dataset as it avoids unnecessary conversion round-trips. Note: The Graph implementation is not required to keep the JVM object reference, e.g. after g2.add(subj1, pred, obj) it is not required to later return the same subj1 implementation in g2.stream(). Special care should be taken if returned values needs to be casted to implementation specific types, e.g. using the appropriate adapter method from the desired RDF implementation. The .equals() methods of RDFTerm, Triple and Quad are explicitly defined, so their instances can be compared across implementations, and thus can safely be used for instance as keys in a java.util.Map or java.util.Set. Note: Special care might need to be taken for cross-interoperability of BlankNode instances. While multiple triples/quads with the same “foreign” BlankNode can be added without breaking their connections, the Graph/Quad is not required to return blank node instances that .equals() those previously inserted - specifically implementations are not expected to persist the blank node uniqueReference. Complete exampleThe complete source code for the examples used in this user guide can be browsed in UserGuideTest.java within the examples folder of the Commons RDF source code repository. |