package won.protocol.util;
import com.google.common.collect.Iterators;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.*;
import org.apache.jena.rdf.model.*;
import org.apache.jena.rdf.model.impl.StatementImpl;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.riot.RDFFormat;
import org.apache.jena.shared.Lock;
import org.apache.jena.sparql.path.Path;
import org.apache.jena.sparql.path.eval.PathEval;
import org.apache.jena.sparql.util.Context;
import org.apache.jena.tdb.TDB;
import org.apache.jena.util.FileUtils;
import org.apache.jena.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import won.protocol.exception.IncorrectPropertyCountException;
import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Utilities for RDF manipulation with Jena.
*/
public class RdfUtils
{
public static final RDFNode EMPTY_RDF_NODE = null;
private static final CheapInsecureRandomString randomString = new CheapInsecureRandomString();
private static final Logger logger = LoggerFactory.getLogger(RdfUtils.class);
public static String toString(Model model)
{
String ret = "";
if (model != null) {
StringWriter sw = new StringWriter();
model.write(sw, "TTL");
ret = sw.toString();
}
return ret;
}
public static Model toModel(String content)
{
return readRdfSnippet(content, FileUtils.langTurtle);
}
/**
* Converts a Jena Dataset into a TriG string
*
* @param dataset Dataset containing RDF which will be converted
* @return <code>String</code> containing TriG serialized RDF from the dataset
*/
public static String toString(Dataset dataset) {
String result = "";
if (dataset != null) {
StringWriter sw = new StringWriter();
RDFDataMgr.write(sw, dataset, RDFFormat.TRIG.getLang());
result = sw.toString();
}
return result;
}
/**
* Converts a <code>String</code> containing TriG formatted RDF into a Jena Dataset
*
* @param content String with the TriG formatted RDF
* @return Jena Dataset containing the RDF from content
*/
public static Dataset toDataset(String content) {
return toDataset(content, RDFFormat.TRIG);
}
public static Dataset toDataset(String content, RDFFormat rdfFormat) {
if (content != null) {
return toDataset(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), rdfFormat);
} else
return DatasetFactory.createGeneral();
}
public static Dataset toDataset(InputStream stream, RDFFormat rdfFormat) {
Dataset dataset = DatasetFactory.createGeneral();
RDFDataMgr.read(dataset, stream, rdfFormat.getLang());
try {
stream.close();
} catch (IOException ex) {
logger.warn("An exception occurred.", ex);
}
return dataset;
}
/**
* Clones the specified model (its statements and ns prefixes) and returns the clone.
* @param original
* @return
*/
public static Model cloneModel(Model original){
Model clonedModel = ModelFactory.createDefaultModel();
original.enterCriticalSection(Lock.READ);
try {
StmtIterator it = original.listStatements();
while (it.hasNext()){
clonedModel.add(it.nextStatement());
}
clonedModel.setNsPrefixes(original.getNsPrefixMap());
} finally {
original.leaveCriticalSection();
}
return clonedModel;
}
public static Dataset cloneDataset(Dataset dataset) {
if (dataset == null) return null;
Dataset clonedDataset = DatasetFactory.createGeneral();
Model model = dataset.getDefaultModel();
if (model != null) {
clonedDataset.setDefaultModel(cloneModel(model));
}
for (Iterator<String> modelNames = dataset.listNames(); modelNames.hasNext(); ){
String modelName = modelNames.next();
clonedDataset.addNamedModel(modelName, cloneModel(dataset.getNamedModel(modelName)));
}
return clonedDataset;
}
public static void replaceBaseURI(final Model model, final String baseURI)
{
//we assume that the RDF content is self-referential, i.e., it 'talks about itself': the graph is connected to
//the public resource URI which, when de-referenced, returns that graph. So, triples referring to the 'null relative URI'
//(see http://www.w3.org/2012/ldp/track/issues/20 ) will be changed to refer to the newly created need URI instead.
//this implies that the default URI prefix of the document (if set) will have to be changed to the need URI.
//check if there is a default URI prefix.
//- If not, we just change the default prefix and that should automatically alter all
// null relative uris to refer to the newly set prefix.
//- If there is one, fetch it as a resource and 'rename' it (i.e., replace all statements with exchanged name)
if (model.getNsPrefixURI("") != null) {
ResourceUtils.renameResource(
model.getResource(model.getNsPrefixURI("")), baseURI
);
}
//whatever the base uri (default URI prefix) was, set it to the need URI.
model.setNsPrefix("", baseURI);
}
/**
* Replaces the base URI that's set as the model's default URI prfefix in all statements by replacement.
*
* @param model
* @param replacement
*/
public static void replaceBaseResource(final Model model, final Resource replacement)
{
String baseURI = model.getNsPrefixURI("");
if (baseURI == null) return;
Resource baseUriResource = model.getResource(baseURI);
replaceResourceInModel(baseUriResource, replacement);
model.setNsPrefix("", replacement.getURI());
}
/**
* Modifies the specified resources' model, replacing resource with replacement.
* @param resource
* @param replacement
*/
private static void replaceResourceInModel(final Resource resource, final Resource replacement)
{
logger.debug("replacing resource '{}' with resource '{}'", resource, replacement);
if (!resource.getModel().equals(replacement.getModel())) throw new IllegalArgumentException("resource and replacement must be from the same model");
Model model = resource.getModel();
Model modelForNewStatements = ModelFactory.createDefaultModel();
StmtIterator iterator = model.listStatements(resource, (Property) null, (RDFNode) null);
while (iterator.hasNext()) {
Statement origStmt = iterator.next();
Statement newStmt = new StatementImpl(replacement, origStmt.getPredicate(), origStmt.getObject());
iterator.remove();
modelForNewStatements.add(newStmt);
}
iterator = model.listStatements(null, (Property) null, (RDFNode) resource);
while (iterator.hasNext()) {
Statement origStmt = iterator.next();
Statement newStmt = new StatementImpl(origStmt.getSubject(), origStmt.getPredicate(), replacement);
iterator.remove();
modelForNewStatements.add(newStmt);
}
model.add(modelForNewStatements);
}
/**
* Creates a new model that contains both specified models' content. The base resource is that of model1,
* all triples in model2 that are attached to the its base resource are modified so as to be attached to the
* base resource of the result.
* @param model1
* @param model2
* @return
*/
public static Model mergeModelsCombiningBaseResource(final Model model1, final Model model2){
if (logger.isDebugEnabled()){
logger.debug("model1:\n{}",writeModelToString(model1, Lang.TTL));
logger.debug("model2:\n{}",writeModelToString(model2, Lang.TTL));
}
Model result = ModelFactory.createDefaultModel();
result.setNsPrefixes(mergeNsPrefixes(model1.getNsPrefixMap(), model2.getNsPrefixMap()));
result.add(model1);
result.add(model2);
if (logger.isDebugEnabled()){
logger.debug("result (before merging base resources):\n{}",writeModelToString(result, Lang.TTL));
}
Resource baseResource1 = getBaseResource(model1);
Resource baseResource2 = getBaseResource(model2);
replaceResourceInModel(result.getResource(baseResource1.getURI()), result.getResource(baseResource2.getURI()));
String prefix = model1.getNsPrefixURI("");
if (prefix != null) {
result.setNsPrefix("", prefix);
}
if (logger.isDebugEnabled()){
logger.debug("result (after merging base resources):\n{}",writeModelToString(result, Lang.TTL));
}
return result;
}
/**
* Finds the resource representing the model's base resource, i.e. the resource with the
* model's base URI. If no such URI is specified, a dummy base URI is set and a resource is
* returned referencing that URI.
*
* @param model
* @return
*/
public static Resource findOrCreateBaseResource(Model model) {
String baseURI = model.getNsPrefixURI("");
if (baseURI == null){
model.setNsPrefix("","no:uri");
baseURI = model.getNsPrefixURI("");
}
return model.getResource(baseURI);
}
/**
* Returns the resource representing the model's base resource, i.e. the resource with the
* model's base URI.
* @param model
* @return
*/
public static Resource getBaseResource(Model model){
String baseURI = model.getNsPrefixURI("");
if (baseURI == null) {
return model.getResource("");
} else {
return model.getResource(baseURI);
}
}
public static String writeModelToString(final Model model, final Lang lang)
{
StringWriter out = new StringWriter();
RDFDataMgr.write(out, model, lang);
return out.toString();
}
/**
* Returns a copy of the specified resources' model where resource is replaced by replacement.
* @param resource
* @param replacement
* @return
*/
public static Model replaceResource(Resource resource, Resource replacement){
if (!resource.getModel().equals(replacement.getModel())) throw new IllegalArgumentException("resource and replacement must be from the same model");
Model result = ModelFactory.createDefaultModel();
result.setNsPrefixes(resource.getModel().getNsPrefixMap());
StmtIterator it = resource.getModel().listStatements();
while (it.hasNext()){
Statement stmt = it.nextStatement();
Resource subject = stmt.getSubject();
Resource predicate = stmt.getPredicate();
RDFNode object = stmt.getObject();
if (subject.equals(resource)){
subject = replacement;
}
if (predicate.equals(resource)){
predicate = replacement;
}
if (object.equals(resource)){
object = replacement;
}
Triple triple = new Triple(subject.asNode(), predicate.asNode(), object.asNode());
result.getGraph().add(triple);
}
return result;
}
/**
* Adds the specified objectModel to the model of the specified subject. In the objectModel, the resource
* that is identified by the objectModel's base URI (the "" URI prefix) will be replaced by a newly created
* blank node(B, see later). All content of the objectModel is added to the model of the subject. An
* additional triple (subject, property, B) is added. Moreover, the Namespace prefixes are merged.
* @param subject
* @param property
* @param objectModel caution - will be modified
*/
public static void attachModelByBaseResource(final Resource subject, final Property property, final Model objectModel)
{
attachModelByBaseResource(subject, property, objectModel, true);
}
/**
* Adds the specified objectModel to the model of the specified subject. In the objectModel, the resource
* that is identified by the objectModel's base URI (the "" URI prefix) will be replaced by a newly created
* blank node(B, see later). All content of the objectModel is added to the model of the subject. An
* additional triple (subject, property, B) is added. Moreover, the Namespace prefixes are merged.
* @param subject
* @param property
* @param objectModel caution - will be modified
* @param replaceBaseResourceByBlankNode
*/
public static void attachModelByBaseResource(final Resource subject, final Property property, final Model objectModel, final boolean replaceBaseResourceByBlankNode){
Model subjectModel = subject.getModel();
//either explicitly use blank node, or do so if there is no base resource prefix
//as the model may have triples containing the null relative URI.
// we still want to attach these and try to get them by using
//the empty URI, and replacing that resource by a blank node
if (replaceBaseResourceByBlankNode || objectModel.getNsPrefixURI("") == null){
//create temporary resource and replace objectModel's base resource to avoid clashes
String tempURI = "tmp:"+Integer.toHexString(objectModel.hashCode());
replaceBaseResource(objectModel, objectModel.createResource(tempURI));
//merge models
subjectModel.add(objectModel);
//replace temporary resource by blank node
Resource blankNode = subjectModel.createResource();
subject.addProperty(property, blankNode);
replaceResourceInModel(subjectModel.getResource(tempURI), blankNode);
//merge the prefixes, but don't add the objectModel's default prefix in any case - we don't want it to end up as
//the resulting model's base prefix.
Map<String, String> objectModelPrefixes = objectModel.getNsPrefixMap();
objectModelPrefixes.remove("");
subjectModel.setNsPrefixes(mergeNsPrefixes(subjectModel.getNsPrefixMap(), objectModelPrefixes));
} else {
String baseURI = objectModel.getNsPrefixURI("");
Resource baseResource = objectModel.getResource(baseURI); //getResource because it may exist already
subjectModel.add(objectModel);
baseResource = subjectModel.getResource(baseResource.getURI());
subject.addProperty(property, baseResource);
RdfUtils.replaceBaseResource(subjectModel, baseResource);
}
}
/**
* Creates a new Map object containing all prefixes from both specified maps. When prefix mappings clash, the mappings
* from prioritaryPrefixes are used.
* @param prioritaryPrefixes
* @param additionalPrefixes
* @return
*/
public static Map<String, String> mergeNsPrefixes(final Map<String, String> prioritaryPrefixes, final Map<String, String> additionalPrefixes)
{
Map<String, String> mergedPrefixes = new HashMap<String, String>();
mergedPrefixes.putAll(additionalPrefixes);
mergedPrefixes.putAll(prioritaryPrefixes); //overwrites the additional prefixes when clashing
return mergedPrefixes;
}
/**
* Reads the InputStream into a Model. Sets a 'fantasy URI' as base URI and handles it gracefully if
* the model read from the string defines its own base URI. Special care is taken that the null relative URI ('<>')
* is replaced by the baseURI.
* @param in
* @param rdfLanguage
* @return a Model (possibly empty)
*/
public static Model readRdfSnippet(final InputStream in, final String rdfLanguage)
{
org.apache.jena.rdf.model.Model model = ModelFactory.createDefaultModel();
if (in == null) return model;
String baseURI= "no:uri";
model.setNsPrefix("", baseURI);
model.read(in, baseURI, rdfLanguage);
String baseURIAfterReading = model.getNsPrefixURI("");
if (baseURIAfterReading == null){
model.setNsPrefix("",baseURI);
} else if (!baseURI.equals(baseURIAfterReading)){
//the string representation of the model specified a base URI, but we used a different one for reading.
//We need to make sure that the one that is now set is the only one used
ResourceUtils.renameResource(model.getResource(baseURI), model.getNsPrefixURI(""));
}
return model;
}
/**
* Reads the InputStream into a Model. Sets a 'fantasy URI' as base URI and handles it gracefully if
* the model read from the string defines its own base URI. Special care is taken that the null relative URI ('<>')
* is replaced by the baseURI.
* @param in
* @param rdfLanguage
* @return a Model (possibly empty)
*/
public static Model readRdfSnippet(final Reader in, final String rdfLanguage)
{
org.apache.jena.rdf.model.Model model = ModelFactory.createDefaultModel();
if (in == null) return model;
String baseURI= "no:uri";
model.setNsPrefix("", baseURI);
model.read(in, baseURI, rdfLanguage);
String baseURIAfterReading = model.getNsPrefixURI("");
if (baseURIAfterReading == null){
model.setNsPrefix("",baseURI);
} else if (!baseURI.equals(baseURIAfterReading)){
//the string representation of the model specified a base URI, but we used a different one for reading.
//We need to make sure that the one that is now set is the only one used
ResourceUtils.renameResource(model.getResource(baseURI), model.getNsPrefixURI(""));
}
return model;
}
/**
* Reads the specified string into a Model. Sets a 'fantasy URI' as base URI and handles it gracefully if
* the model read from the string defines its own base URI. Special care is taken that the null relative URI ('<>')
* is replaced by the baseURI.
* @param rdfAsString
* @param rdfLanguage
* @return a Model (possibly empty)
*/
public static Model readRdfSnippet(final String rdfAsString, final String rdfLanguage)
{
return readRdfSnippet(new StringReader(rdfAsString), rdfLanguage);
}
/**
* Evaluates the path on the model obtained by dereferencing the specified resourceURI.
* If the path resolves to multiple resources, only the first one is returned.
* <br />
* <br />
* Note: For more information on property paths, see http://jena.sourceforge.net/ARQ/property_paths.html
* <br />
* To create a Path object for the path "rdf:type/rdfs:subClassOf*":
* <pre>
* Path path = PathParser.parse("rdf:type/rdfs:subClassOf*", PrefixMapping.Standard) ;
* </pre>
* @param resourceURI
* @param propertyPath
* @return null if the model is empty or the path does not resolve to a node
* @throws IllegalArgumentException if the node found by the path is not a URI
*/
public static URI getURIPropertyForPropertyPath(final Model model, final URI resourceURI, Path propertyPath)
{
return toURI(getNodeForPropertyPath(model, resourceURI, propertyPath));
}
/**
* Evaluates the path on all models in the dataset obtained by dereferencing the specified resourceURI.
* If the path resolves to multiple resources, only the first one is returned.
* <br />
* <br />
* Note: For more information on property paths, see http://jena.sourceforge.net/ARQ/property_paths.html
* <br />
* To create a Path object for the path "rdf:type/rdfs:subClassOf*":
* <pre>
* Path path = PathParser.parse("rdf:type/rdfs:subClassOf*", PrefixMapping.Standard) ;
* </pre>
* @param resourceURI
* @param propertyPath
* @return null if the model is empty or the path does not resolve to a node
* @throws IllegalArgumentException if the node found by the path is not a URI
*/
public static URI getURIPropertyForPropertyPath(final Dataset dataset, final URI resourceURI, Path propertyPath)
{
return toURI(getNodeForPropertyPath(dataset, resourceURI, propertyPath));
}
public static Iterator<URI> getURIsForPropertyPath(final Model model, final URI resourceURI, Path propertyPath)
{
Iterator<Node> nodeIterator = getNodesForPropertyPath(model,resourceURI,propertyPath);
return new ProjectingIterator<Node, URI>(nodeIterator) {
@Override
public URI next() {
return toURI(this.baseIterator.next());
}
};
}
public static Iterator<URI> getURIsForPropertyPath(final Dataset dataset, final URI resourceURI, Path propertyPath)
{
Iterator<Node> nodeIterator = getNodesForPropertyPath(dataset,resourceURI,propertyPath);
return new ProjectingIterator<Node, URI>(nodeIterator) {
@Override
public URI next() {
return toURI(this.baseIterator.next());
}
};
}
/**
* Evaluates the property path by executing a sparql query.
* @param dataset
* @param resourceURI
* @param propertyPath
* @return
*/
public static Iterator<RDFNode> getNodesForPropertyPathByQuery(final Dataset dataset, final URI resourceURI, Path propertyPath)
{
String queryString = "select ?obj where { ?resource " + propertyPath.toString() +" ?obj}";
Query query = QueryFactory.create(queryString);
QuerySolutionMap initialBinding = new QuerySolutionMap();
initialBinding.add("?resource",dataset.getDefaultModel().createResource(resourceURI.toString()));
QueryExecution qExec = QueryExecutionFactory.create(query, dataset,initialBinding);
qExec.getContext().set(TDB.symUnionDefaultGraph, true) ;
try {
final ResultSet results = qExec.execSelect();
LinkedList<RDFNode> resultNodes = new LinkedList<>();
while (results.hasNext()) {
QuerySolution soln = results.next();
RDFNode result = soln.get("obj");
resultNodes.add(result);
}
return resultNodes.iterator();
} finally {
if (!qExec.isClosed()){
qExec.close();
}
}
}
/**
* Sets the vars of a given sparql query
* Replaces every instance of ::var:: with the given object (this can only be an URI or a List of URIS at the moment)
* @param stmt
* @param var that will be replaced
* @param obj object that is replacing the variable
* @return replaced statement
*/
public static String setSparqlVars(String stmt, String var, Object obj){
StringBuilder replacement = new StringBuilder();
if(obj instanceof URI){
replacement.append("<").append(obj.toString()).append(">");
}else if(obj instanceof List){
for(Object itm : (List)obj){
if(itm instanceof URI){
replacement.append("<").append(itm.toString()).append(">,");
}
}
replacement.deleteCharAt(replacement.length()-1);
}
return stmt.replaceAll("::"+var+"::",replacement.toString());
}
/**
* Sets the vars of a given sparql query
* Replaces every instance of ::var:: with the given object (this can only be an URI or a List of URIS at the moment)
* @param stmt
* @param varMap replaces the key with the object within the given statement
* @return replaced statement
*/
public static String setSparqlVars(String stmt, Map<String, Object> varMap) {
for(Map.Entry<String, Object> entry : varMap.entrySet()){
stmt = setSparqlVars(stmt, entry.getKey(), entry.getValue());
}
return stmt;
}
/**
* Evaluates a property path on the specified dataset by executing a sparql query and returns an iterator of URIs.
* @param dataset
* @param resourceURI
* @param propertyPath
* @return
*/
public static Iterator<URI> getURIsForPropertyPathByQuery(final Dataset dataset, final URI resourceURI, Path propertyPath)
{
Iterator<RDFNode> nodeIterator = getNodesForPropertyPathByQuery(dataset, resourceURI, propertyPath);
return new ProjectingIterator<RDFNode, URI>(nodeIterator) {
@Override
public URI next() {
return toURI(this.baseIterator.next());
}
};
}
/**
* Evaluates the path on the model obtained by dereferencing the specified resourceURI.
* If the path resolves to multiple resources, only the first one is returned.
* <br />
* <br />
* Note: For more information on property paths, see http://jena.sourceforge.net/ARQ/property_paths.html
* <br />
* To create a Path object for the path "rdf:type/rdfs:subClassOf*":
* <pre>
* Path path = PathParser.parse("rdf:type/rdfs:subClassOf*", PrefixMapping.Standard) ;
* </pre>
* @param resourceURI
* @param propertyPath
* @return null if the model is empty or the path does not resolve to a node
*/
public static String getStringPropertyForPropertyPath(final Model model, final URI resourceURI, Path propertyPath)
{
return toString(getNodeForPropertyPath(model, resourceURI, propertyPath));
}
/**
* Evaluates the path on each model in the dataset obtained by dereferencing the specified resourceURI.
* If the path resolves to multiple resources, only the first one is returned.
* <br />
* <br />
* Note: For more information on property paths, see http://jena.sourceforge.net/ARQ/property_paths.html
* <br />
* To create a Path object for the path "rdf:type/rdfs:subClassOf*":
* <pre>
* Path path = PathParser.parse("rdf:type/rdfs:subClassOf*", PrefixMapping.Standard) ;
* </pre>
* @param resourceURI
* @param propertyPath
* @return null if the model is empty or the path does not resolve to a node
*/
public static String getStringPropertyForPropertyPath(final Dataset dataset, final URI resourceURI, Path propertyPath)
{
return toString(getNodeForPropertyPath(dataset, resourceURI, propertyPath));
}
/**
* Returns the literal lexical form of the specified node or null if the node is null.
* @param node
* @return
*/
public static String toString(Node node){
if (node == null) return null;
return node.getLiteralLexicalForm();
}
/**
* Returns the URI of the specified node or null if the node is null. If the node does not
* represent a resource, an UnsupportedOperationException is thrown.
* @param node
* @return
*/
public static URI toURI(Node node){
if (node == null) return null;
return URI.create(node.getURI());
}
/**
* Returns the URI of the specified RDFNode or null if the node is null. If the node does not
* represent a resource, a ResourceRequiredException is thrown.
* @param node
* @return
*/
public static URI toURI(RDFNode node){
if (node == null) return null;
return URI.create(node.asResource().getURI());
}
/**
* Returns the first RDF node found in the specified model for the specified property path.
* @param model
* @param resourceURI
* @param propertyPath
* @return
*/
public static Node getNodeForPropertyPath(final Model model, URI resourceURI, Path propertyPath) {
//Iterator<Node> result = PathEval.eval(model.getGraph(), model.getResource(resourceURI.toString()).asNode(),
// propertyPath);
Iterator<Node> result = PathEval.eval(model.getGraph(), model.getResource(resourceURI.toString()).asNode(),
propertyPath, Context.emptyContext);
if (!result.hasNext()) return null;
return result.next();
}
public static Node getNodeForPropertyPath(final Model model, Node node, Path propertyPath) {
//Iterator<Node> result = PathEval.eval(model.getGraph(), model.getResource(resourceURI.toString()).asNode(),
// propertyPath);
Iterator<Node> result = PathEval.eval(model.getGraph(), node, propertyPath, Context.emptyContext);
if (!result.hasNext()) return null;
return result.next();
}
/**
* Returns the first RDF node found in the specified dataset for the specified property path.
* @param dataset
* @param resourceURI
* @param propertyPath
* @return
*/
public static Node getNodeForPropertyPath(final Dataset dataset, final URI resourceURI, final Path propertyPath) {
return findFirst(dataset, new ModelVisitor<Node>()
{
@Override
public Node visit(final Model model) {
return getNodeForPropertyPath(model, resourceURI, propertyPath);
}
});
}
/**
* Evaluates the specified path in the specified model, starting with the specified resourceURI.
* @param model
* @param resourceURI
* @param propertyPath
* @return
*/
public static Iterator<Node> getNodesForPropertyPath(final Model model, URI resourceURI, Path propertyPath) {
Iterator<Node> result = PathEval.eval(model.getGraph(), model.getResource(resourceURI.toString()).asNode(),
propertyPath, Context.emptyContext);
return result;
}
/**
* Evaluates the specified path in each model of the specified dataset, starting with the specified resourceURI.
* @param dataset
* @param resourceURI
* @param propertyPath
* @return
*/
public static Iterator<Node> getNodesForPropertyPath(final Dataset dataset, final URI resourceURI, final Path propertyPath) {
return Iterators.concat(
visit(dataset, new ModelVisitor<Iterator<Node>>()
{
@Override
public Iterator<Node> visit(final Model model) {
return getNodesForPropertyPath(model, resourceURI, propertyPath);
}
})
);
}
public static URI toUriOrNull(final Object uriStringOrNull) {
if (uriStringOrNull == null) return null;
return URI.create(uriStringOrNull.toString());
}
/**
* Dataset visitor used for repeated application of model operations in a dataset.
*/
public static interface ModelVisitor<T> {
public T visit(Model model);
}
/**
* ModelSelector used to select which models in a dataset to visit.
*/
public static interface ModelSelector {
public Iterator<Model> select(Dataset dataset);
}
/**
* ResultCombiner which combines to results of type T and returns it.
*/
public static interface ResultCombiner<T> {
public T combine(T first, T second);
}
/**
* Selector that selects all models, including the default model.
* The first model is the default model, the named models are returned in the order
* specified by Dataset.listNames().
*/
public static class DefaultModelSelector implements ModelSelector{
@Override
public Iterator<Model> select(final Dataset dataset) {
List ret = new LinkedList<Model>();
Model model = dataset.getDefaultModel();
if (model != null) {
ret.add(model);
}
for (Iterator<String> modelNames = dataset.listNames(); modelNames.hasNext(); ){
ret.add(dataset.getNamedModel(modelNames.next()));
}
return ret.iterator();
}
}
private static ModelSelector DEFAULT_MODEL_SELECTOR = new DefaultModelSelector();
/**
* Returns a thread-safe, shared default model selector.
* @return
*/
public static ModelSelector getDefaultModelSelector(){
return DEFAULT_MODEL_SELECTOR;
};
/**
* Calls the specified ModelVisitor's visit method on each model of the dataset that is selected by the ModelSelector.
* The rsults are collected in the returned iterator.
* @param dataset
* @param visitor
* @param modelSelector
* @param <T>
* @return
*/
public static <T> Iterator<T> visit(Dataset dataset, ModelVisitor<T> visitor, ModelSelector modelSelector){
List<T> results = new LinkedList<T>();
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();){
results.add(visitor.visit(modelIterator.next()));
}
return results.iterator();
}
public static <T> Iterator<T> visit(Dataset dataset, ModelVisitor<T> visitor){
return visit(dataset, visitor, getDefaultModelSelector());
}
/**
* Visits all models and flattens the collections returned by the visitor into one list.
* @param dataset
* @param visitor
* @param modelSelector
* @param <T>
* @return
*/
public static <E, T extends Collection<E>> List<E> visitFlattenedToList(Dataset dataset, ModelVisitor<T> visitor,
ModelSelector modelSelector){
List<E> results = new ArrayList<E>();
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();){
results.addAll(visitor.visit(modelIterator.next()));
}
return results;
}
public static <E, T extends Collection<E>> List<E> visitFlattenedToList(Dataset dataset, ModelVisitor<T> visitor) {
return visitFlattenedToList(dataset, visitor, getDefaultModelSelector());
}
/**
* Visits all models and flattens the NodeIterator returned by the visitor into one.
* Returns null if all visitors return null.
* @param dataset
* @param visitor
* @param modelSelector
* @return
*/
public static NodeIterator visitFlattenedToNodeIterator(Dataset dataset,
ModelVisitor<NodeIterator> visitor,
ModelSelector modelSelector){
NodeIterator it = null;
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();){
NodeIterator currentIt = visitor.visit(modelIterator.next());
if (it == null) {
it = currentIt;
} else {
it.andThen(currentIt);
}
}
return it;
}
public static NodeIterator visitFlattenedToNodeIterator(Dataset dataset,
ModelVisitor<NodeIterator> visitor){
return visitFlattenedToNodeIterator(dataset, visitor, getDefaultModelSelector());
}
/**
* Returns the first non-null result obtained by calling the specified ModelVisitor's visit method in the order
* defined by the specified ModelSelector.
* @param dataset
* @param visitor
* @param modelSelector
* @param <T>
* @return
*/
public static <T> T findFirst(Dataset dataset, ModelVisitor<T> visitor, ModelSelector modelSelector){
List<T> results = new LinkedList<T>();
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();){
T result = visitor.visit(modelIterator.next());
if (result != null) return result;
}
return null;
}
public static <T> T findFirst(Dataset dataset, ModelVisitor<T> visitor){
return findFirst(dataset, visitor, getDefaultModelSelector());
}
/**
* Returns the result obtained by calling the specified ModelVisitor's visit method in the order
* defined by the specified ModelSelector. Throws an IncorrectPropertyCountException if no result or
* more than one result is found. ModelVisitors should implement the same exception strategy.
* @param dataset
* @param visitor
* @param modelSelector
* @param allowSame if true, multiple results will be checked with equals() and if they are are equal, no
* exception is thrown
* @param <T>
* @return
*/
public static <T> T findOne(Dataset dataset, ModelVisitor<T> visitor, ModelSelector modelSelector, boolean allowSame){
T result = null;
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();) {
T newResult = visitor.visit(modelIterator.next());
if (newResult != null) {
if (result != null) {
if (!allowSame || !result.equals(newResult)) {
throw new IncorrectPropertyCountException("Results were found in more than " +
"one model", 1, 2);
}
}
result = newResult;
}
}
if (result == null)
throw new IncorrectPropertyCountException("No result found", 1, 0);
return result;
}
public static <T> T findOne(Dataset dataset, ModelVisitor<T> visitor, boolean allowSame){
return findOne(dataset, visitor, getDefaultModelSelector(), allowSame);
}
/**
* Returns the result obtained by calling the specified ModelVisitor's visit method where the
* result is combined with the ResultCombiner's combine method.
* @param dataset
* @param visitor
* @param modelSelector
* @param resultCombiner
* @param <T>
* @return
*/
public static <T> T applyMethod(Dataset dataset, ModelVisitor<T> visitor,
ModelSelector modelSelector,
ResultCombiner<T> resultCombiner) {
T result = null;
for (Iterator<Model> modelIterator = modelSelector.select(dataset); modelIterator.hasNext();) {
T newResult = visitor.visit(modelIterator.next());
if (result != null)
result = resultCombiner.combine(result, newResult);
else
result = newResult;
}
return result;
}
public static <T> T applyMethod(Dataset dataset,
ModelVisitor<T> visitor,
ResultCombiner<T> resultCombiner) {
return applyMethod(dataset, visitor, getDefaultModelSelector(), resultCombiner);
}
/**
* Finds resource which is a specified property of a specified resource.
* If multiple (non equal) resources are found an exception is thrown.
*
*
* @param dataset <code>Dataset</code> to look into
* @param resourceURI
* @param p
* @return <code>URI</code> of the resource
*/
public static RDFNode findOnePropertyFromResource(Dataset dataset, final URI resourceURI, final Property p) {
return RdfUtils.findOne(dataset, new RdfUtils.ModelVisitor<RDFNode>()
{
@Override
public RDFNode visit(final Model model) {
return findOnePropertyFromResource(model, resourceURI, p);
}
}, true);
}
public static RDFNode findOnePropertyFromResource(Dataset dataset, final Resource resource, final Property p) {
return RdfUtils.findOne(dataset, new RdfUtils.ModelVisitor<RDFNode>()
{
@Override
public RDFNode visit(final Model model) {
return findOnePropertyFromResource(model, resource, p);
}
}, true);
}
/**
* Finds resource which is a specified property of a specified resource.
* If multiple (non equal) resources are found an exception is thrown.
*
*
* @param model <code>Model</code> to look into
* @param resourceURI
* @param property
* @return <code>URI</code> of the resource
*/
public static RDFNode findOnePropertyFromResource(Model model, URI resourceURI, Property property) {
Resource resource = null;
if (resourceURI != null) {
resource = model.getResource(resourceURI.toString());
}
return findOnePropertyFromResource(model, resource, property);
}
public static RDFNode findOnePropertyFromResource(final Model model, final Resource resource, final Property property) {
List<RDFNode> foundNodes = new ArrayList<RDFNode>();
NodeIterator iterator = model.listObjectsOfProperty(resource, property);
while (iterator.hasNext()) {
foundNodes.add(iterator.next());
}
if (foundNodes.size() == 0)
return null;
else if (foundNodes.size() == 1)
return foundNodes.get(0);
else if (foundNodes.size() > 1) {
RDFNode n = foundNodes.get(0);
for (RDFNode node : foundNodes) {
if (!node.equals(n))
throw new IncorrectPropertyCountException(1,2);
}
return n;
}
else
return null;
}
public static Resource findOneSubjectResource(Dataset dataset, Property property, RDFNode object) {
return RdfUtils.findOne(dataset, new RdfUtils.ModelVisitor<Resource>()
{
@Override
public Resource visit(final Model model) {
return findOneSubjectResource(model, property, object);
}
}, true);
}
public static Resource findOneSubjectResource(Model model, Property property, RDFNode object) {
Resource resource = null;
ResIterator iter = model.listSubjectsWithProperty(property, object);
while (iter.hasNext()) {
resource = iter.next();
if (iter.hasNext()) {
throw new IncorrectPropertyCountException("expecting exactly one subject resource for property " +
property.getURI() + " and object " + object.toString(), 1, 2);
}
}
if (resource == null) {
throw new IncorrectPropertyCountException("expecting exactly one subject resource for property " +
property.getURI() + " and object " + object.toString(), 1, 0);
}
return resource;
}
/**
* Finds the first triple in the specified model that has the specified propery and object.
* The subject is expected to be a resource.
* @param model
* @param property
* @param object
* @param allowMultiple if false, will throw an IllegalArgumentException if more than one triple is found
* @param allowNone if false, will throw an IllegalArgumentException if no triple is found
* @return
*/
public static URI findFirstSubjectUri(Model model, Property property, RDFNode object, boolean allowMultiple,
boolean allowNone){
URI retVal = null;
StmtIterator it = model.listStatements(null, property, object);
if (!it.hasNext() && !allowNone) throw new IllegalArgumentException("expecting at least one triple");
if (it.hasNext()){
retVal = URI.create(it.nextStatement().getSubject().asResource().toString());
}
if (!allowMultiple && it.hasNext()) throw new IllegalArgumentException("not expecting more than one triple");
return retVal;
}
public static URI findFirstObjectUri(Model model, Property property, RDFNode object, boolean allowMultiple,
boolean allowNone){
URI retVal = null;
StmtIterator it = model.listStatements(null, property, object);
if (!it.hasNext() && !allowNone) throw new IllegalArgumentException("expecting at least one triple");
if (it.hasNext()){
retVal = URI.create(it.nextStatement().getObject().asResource().toString());
}
if (!allowMultiple && it.hasNext()) throw new IllegalArgumentException("not expecting more than one triple");
return retVal;
}
/**
* Creates a new graph URI for the specified dataset by appending
* a specified string (toAppend) and then n alphanumeric characters to the
* specified String.
* It is guaranteed that the resulting URI is not used as a graph
* name in the specified dataset.
*
* Note that the implementation is not synchronized, so concurrent
* executions of the method may result in identical URIs being returned.
*
* If both the specified baseURI and the toAppend string contain a hash sign ('#'),
* the hash-part will be removed from the base uri before the result will be crated.
*
* @param baseURI the URI to be extended.
* @param toAppend a string that will be appended directly to the URI.
* @param length number of alphanumeric characters that are appended to <code>toAppend</code>.
* @param dataset the dataset that will be checked to determine if the resulting URI is new.
* @return an URI that is previously unused as a graph URI.
*/
public static URI createNewGraphURI(String baseURI, String toAppend, int length, final Dataset dataset){
return createNewGraphURI(baseURI, toAppend, length,
new GraphNameCheck()
{
@Override
public boolean isGraphUriOk(final String graphUri) {
return !dataset.containsNamedModel(graphUri);
}
}
);
}
/**
* Creates a new graph URI for the specified dataset by appending
* a specified string (toAppend) and then n alphanumeric characters to the
* specified String.
* It is guaranteed that the resulting URI is not used as a graph
* name in the specified dataset.
*
* Note that the implementation is not synchronized, so concurrent
* executions of the method may result in identical URIs being returned.
*
* If both the specified baseURI and the toAppend string contain a hash sign ('#'),
* the hash-part will be removed from the base uri before the result will be crated.
*
* @param baseURI the URI to be extended.
* @param toAppend a string that will be appended directly to the URI.
* @param length number of alphanumeric characters that are appended to <code>toAppend</code>.
* @return an URI that is previously unused as a graph URI.
*/
public static URI createNewGraphURI(String baseURI, String toAppend, int length, GraphNameCheck check){
if (toAppend.contains("#")){
int hashIndex = baseURI.indexOf('#');
if (hashIndex > -1){
baseURI = baseURI.substring(0,hashIndex);
}
}
int maxTries = 5;
for (int i = 0; i < maxTries; i++){
String graphName = baseURI + toAppend + randomString.nextString(length);
if (check.isGraphUriOk(graphName)){
return URI.create(graphName);
}
}
throw new IllegalStateException("Tried " + maxTries +" times to generate a new graph URI (" + length + " random" +
" characters), but were unable to generate a previously unused one; giving up.");
}
public static void addAllStatements(Model toModel, Model fromModel) {
StmtIterator stmtIterator = fromModel.listStatements();
while (stmtIterator.hasNext()) {
toModel.add(stmtIterator.nextStatement());
}
}
public static void addPrefixMapping(Model toModel, Model fromModel) {
for (String prefix : fromModel.getNsPrefixMap().keySet()) {
String uri = toModel.getNsPrefixURI(prefix);
if (uri == null) { // if no such prefix-uri yet, add it
toModel.setNsPrefix(prefix, fromModel.getNsPrefixURI(prefix));
} else {
if (uri.equals(fromModel.getNsPrefixURI(prefix))) {
// prefix-uri is already there, do nothing
} else {
// prefix-uri collision, redefine prefix
int counter = 2;
while (!toModel.getNsPrefixMap().containsKey(prefix + counter)) {
counter++;
}
toModel.setNsPrefix(prefix + counter, fromModel.getNsPrefixURI(prefix));
}
}
}
}
public static List<String> getModelNames(Dataset dataset) {
List<String> modelNames = new ArrayList<String>();
Iterator<String> names = dataset.listNames();
while (names.hasNext()) {
modelNames.add(names.next());
}
return modelNames;
}
public static String writeDatasetToString(final Dataset dataset, final Lang lang)
{
StringWriter sw = new StringWriter();
RDFDataMgr.write(sw, dataset, lang);
return sw.toString();
}
public static Dataset readDatasetFromString(final String data, final Lang lang)
{
StringReader sr = new StringReader(data);
Dataset dataset = DatasetFactory.createGeneral();
RDFDataMgr.read(dataset, sr, "no:uri", lang);
return dataset;
}
/**
* Adds the second dataset to the first one, merging default models and models with identical name.
* @param baseDataset
* @param toBeAddedtoBase
* @param replaceNamedModel if true, named graphs are not merged but replaced
*/
public static void addDatasetToDataset(final Dataset baseDataset, final Dataset toBeAddedtoBase, boolean replaceNamedModel) {
assert baseDataset != null : "baseDataset must not be null";
assert toBeAddedtoBase != null : "toBeAddedToBase must not be null";
baseDataset.getDefaultModel().add(toBeAddedtoBase.getDefaultModel());
for ( Iterator<String> nameIt = toBeAddedtoBase.listNames(); nameIt.hasNext();){
String modelName = nameIt.next();
if (baseDataset.containsNamedModel(modelName)){
if (replaceNamedModel) {
baseDataset.removeNamedModel(modelName);
baseDataset.addNamedModel(modelName, toBeAddedtoBase.getNamedModel(modelName));
} else {
baseDataset.getNamedModel(modelName).add(toBeAddedtoBase.getNamedModel(modelName));
}
} else {
baseDataset.addNamedModel(modelName, toBeAddedtoBase.getNamedModel(modelName));
}
}
}
/**
* Adds the second dataset to the first one, merging default models and models with identical name.
* @param baseDataset
* @param toBeAddedtoBase
*/
public static void addDatasetToDataset(final Dataset baseDataset, final Dataset toBeAddedtoBase) {
addDatasetToDataset(baseDataset, toBeAddedtoBase, false);
}
/**
* Adds all triples of the dataset to the model.
* @param dataset
* @param model
*/
public static void copyDatasetTriplesToModel(final Dataset dataset, final Model model) {
assert dataset != null : "dataset must not be null";
assert model != null : "model must not be null";
visit(dataset, new ModelVisitor<Object>()
{
@Override
public Object visit(final Model datasetModel) {
model.add(datasetModel);
return null;
}
});
}
public static interface GraphNameCheck
{
public boolean isGraphUriOk(String graphUri);
}
}