package net.enilink.komma.internal.rdf4j; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Namespace; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.MalformedQueryException; import org.eclipse.rdf4j.query.Operation; import org.eclipse.rdf4j.query.Query; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.Update; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.RepositoryException; import com.google.inject.Inject; import com.google.inject.Injector; import net.enilink.commons.iterator.Filter; import net.enilink.commons.iterator.IExtendedIterator; import net.enilink.komma.core.INamespace; import net.enilink.komma.core.IReference; import net.enilink.komma.core.IStatement; import net.enilink.komma.core.IStatementPattern; import net.enilink.komma.core.ITransaction; import net.enilink.komma.core.IValue; import net.enilink.komma.core.InferencingCapability; import net.enilink.komma.core.KommaException; import net.enilink.komma.core.Statement; import net.enilink.komma.core.URIs; import net.enilink.komma.dm.IDataManager; import net.enilink.komma.dm.IDataManagerQuery; import net.enilink.komma.dm.IDataManagerUpdate; import net.enilink.komma.dm.change.IDataChangeSupport; import net.enilink.komma.internal.rdf4j.result.RDF4JGraphResult; import net.enilink.komma.internal.rdf4j.result.RDF4JResult; import net.enilink.komma.rdf4j.RDF4JValueConverter; public class RDF4JRepositoryDataManager implements IDataManager { protected static final IReference[] NULL_CTX = { null }; protected IDataChangeSupport changeSupport; protected RepositoryConnection connection; @Inject(optional = true) protected volatile InferencingCapability inferencing; @Inject protected Injector injector; @Inject protected Repository repository; protected RDF4JTransaction transaction; @Inject protected RDF4JValueConverter valueConverter; @Inject public RDF4JRepositoryDataManager(Repository repository, IDataChangeSupport changeSupport) { try { connection = repository.getConnection(); } catch (Exception e) { throw new KommaException(e); } this.changeSupport = changeSupport; this.transaction = new RDF4JTransaction(this, changeSupport); } protected IReference[] addNullContext(boolean includeInferred, IReference[] contexts) { if (includeInferred && getInferencing().inDefaultGraph()) { for (IReference ctx : contexts) { if (ctx == null) { return contexts; } } contexts = Arrays.copyOf(contexts, contexts.length + 1); contexts[contexts.length - 1] = null; } return contexts; } @Override public IDataManager add(Iterable<? extends IStatement> statements, IReference[] readContexts, IReference... addContexts) { if (addContexts.length == 0) { addContexts = NULL_CTX; } IRI[] readCtx = valueConverter.toRdf4jIRI(readContexts); IRI[] addCtx = valueConverter.toRdf4jIRI(addContexts); try { Iterator<? extends IStatement> it = statements.iterator(); RepositoryConnection conn = getConnection(); boolean trackChanges = changeSupport.isEnabled(this); while (it.hasNext()) { IStatement stmt = it.next(); Resource subject = valueConverter.toRdf4j(stmt.getSubject()); IRI predicate = (IRI) valueConverter.toRdf4j(stmt .getPredicate()); Value object = valueConverter.toRdf4j((IValue) stmt .getObject()); if (trackChanges) { if (!conn.hasStatement(subject, predicate, object, false, readCtx)) { for (IReference ctx : addContexts) { changeSupport.add( this, new Statement(stmt.getSubject(), stmt .getPredicate(), stmt.getObject(), ctx, stmt.isInferred())); } } } if (!stmt.isInferred()) { conn.add(subject, predicate, object, addCtx); } } if (!getTransaction().isActive()) { clearNodeMappings(); if (changeSupport.isEnabled(this)) { changeSupport.commit(this); } } } catch (Exception e) { throw new KommaException(e); } return this; } @Override public IDataManager add(Iterable<? extends IStatement> statements, IReference... addContexts) { return add(statements, addContexts, addContexts); } @Override public IDataManager clearNamespaces() { try { if (changeSupport.isEnabled(this)) { for (INamespace namespace : getNamespaces()) { changeSupport.removeNamespace(this, namespace.getPrefix(), namespace.getURI()); } changeSupport.commit(this); } getConnection().clearNamespaces(); } catch (Exception e) { throw new KommaException(e); } return this; } @Override public void close() { if (connection == null) { return; } changeSupport.close(this); try { connection.close(); } catch (Exception e) { throw new KommaException(e); } finally { connection = null; } } protected Query prepareRdf4jQuery(String query, String baseURI, boolean includeInferred) throws MalformedQueryException, RepositoryException { return getConnection().prepareQuery(QueryLanguage.SPARQL, query, baseURI); } protected String ensureBindingsInGraph(String query, IReference[] contexts) { if (contexts.length == 0) { return query; } boolean allNull = true; for (int i = 0; allNull && i < contexts.length; i++) { allNull &= contexts[i] == null; } if (allNull) { return query; } Set<String> variables = new LinkedHashSet<String>(); String delim = " ,)(;.{}"; int i = 0; int qlen = query.length(); int open = 0, closed = 0; while (i < qlen) { int ch = query.charAt(i++); if (ch == '{') { if (variables.size() > 0) { // only projection part of select queries is investigated break; } open++; } else if (ch == '}') { closed++; } else if (ch == '?' || ch == '$') { // variable int j = i; while (j < qlen && delim.indexOf(query.charAt(j)) < 0) { j++; } if (j != i) { String varName = query.substring(i - 1, j).trim(); variables.add(varName); } } if (open > 0 && open == closed) { // only first group pattern is investigated // ASK { this one }, CONSTRUCT { this one } WHERE { ... } break; } } // ensure that at least one statement from the data set mentions a // selected variable int lastBrace = query.lastIndexOf('}'); StringBuilder sb = new StringBuilder(query.substring(0, lastBrace)); int n = 0, g = 0; if (!variables.isEmpty()) { sb.append("\nfilter ("); for (Iterator<String> varIt = variables.iterator(); varIt.hasNext();) { String var = varIt.next(); sb.append("\n(!bound(").append(var).append(") || isLiteral(") .append(var).append(") || exists { graph ?__g") .append(g++).append(" {\n"); sb.append("\t{ ").append(var).append(" ?__p").append(n++) .append(" ?__o").append(n++).append(" } UNION "); sb.append("{ ?__s").append(n++).append(" ?__p").append(n++) .append(" ").append(var).append(" } UNION "); sb.append("{ ?__s").append(n++).append(" ").append(var) .append(" ?__o").append(n++).append(" }\n"); sb.append("} })\n"); if (varIt.hasNext()) { sb.append(" && "); } } sb.append(")"); } sb.append(query.substring(lastBrace)); return sb.toString(); } protected void setDataset(Operation operation, IReference[] readContexts, IReference... modifyContexts) { if (readContexts.length > 0 || modifyContexts.length > 0) { operation.setDataset(valueConverter.createDataset(readContexts, modifyContexts)); } } @Override public <R> IDataManagerQuery<R> createQuery(String query, String baseURI, boolean includeInferred, IReference... contexts) { contexts = addNullContext(includeInferred, contexts); try { // query = ensureBindingsInGraph(query, contexts); Query rdf4jQuery = prepareRdf4jQuery(query, baseURI, includeInferred); setDataset(rdf4jQuery, contexts); rdf4jQuery.setIncludeInferred(includeInferred); RDF4JQuery<R> result = new RDF4JQuery<R>(rdf4jQuery); injector.injectMembers(result); return result; } catch (RepositoryException e) { throw new KommaException(e); } catch (MalformedQueryException e) { throw new KommaException("Invalid query format", e); } } @Override public IDataManagerUpdate createUpdate(final String update, String baseURI, final boolean includeInferred, final IReference... contexts) { return createUpdate(update, baseURI, includeInferred, contexts, contexts); } @Override public IDataManagerUpdate createUpdate(String update, String baseURI, boolean includeInferred, IReference[] readContexts, IReference... modifyContexts) { readContexts = addNullContext(includeInferred, readContexts); if (changeSupport.isEnabled(this)) { RDF4JUpdate result = new RDF4JUpdate(this, update, baseURI, includeInferred, readContexts, modifyContexts); injector.injectMembers(result); return result; } else { try { Update updateOp = getConnection().prepareUpdate( QueryLanguage.SPARQL, update); setDataset(updateOp, readContexts, modifyContexts); updateOp.setIncludeInferred(includeInferred); RDF4JUpdateRemote result = new RDF4JUpdateRemote(updateOp); injector.injectMembers(result); return result; } catch (Exception e) { throw new KommaException(e); } } } protected RepositoryConnection getConnection() { return connection; } @Override public InferencingCapability getInferencing() { if (inferencing == null) { inferencing = new InferencingCapability() { @Override public boolean doesOWL() { return false; } @Override public boolean doesRDFS() { // assume that RDFS is supported return true; } @Override public boolean inDefaultGraph() { return true; } }; } return inferencing; } @Override public net.enilink.komma.core.URI getNamespace(String prefix) { try { String namespaceURI = getConnection().getNamespace(prefix); if (namespaceURI != null) { return net.enilink.komma.core.URIs.createURI(namespaceURI); } return null; } catch (Exception e) { throw new KommaException(e); } } @Override public IExtendedIterator<INamespace> getNamespaces() { IExtendedIterator<INamespace> result = null; try { result = new RDF4JResult<Namespace, INamespace>(getConnection().getNamespaces()) { @Override protected INamespace convert(Namespace element) throws Exception { try { return new net.enilink.komma.core.Namespace( element.getPrefix(), URIs.createURI(element .getName())); } catch (IllegalArgumentException e) { return null; } } }; // "resource leak" warning about result not being closed, // which is of course the intention here // TODO: change to streams and Java 8 filters return result.filterDrop(new Filter<INamespace>() { @Override public boolean accept(INamespace ns) { return ns == null; } }); } catch (Exception e) { if (null != result) { result.close(); } throw new KommaException(e); } } @Override public ITransaction getTransaction() { return transaction; } @Override public boolean hasMatch(IReference subject, IReference predicate, IValue object, boolean includeInferred, IReference... contexts) { contexts = addNullContext(includeInferred, contexts); try { return getConnection().hasStatement( (Resource) valueConverter.toRdf4j(subject), (IRI) valueConverter.toRdf4j(predicate), valueConverter.toRdf4j(object), includeInferred, valueConverter.toRdf4jIRI(contexts)); } catch (Exception e) { throw new KommaException(e); } } @Override public boolean isOpen() { return connection != null; } @Override public IExtendedIterator<IStatement> match(IReference subject, IReference predicate, IValue object, boolean includeInferred, IReference... contexts) { contexts = addNullContext(includeInferred, contexts); try { RDF4JGraphResult result = new RDF4JGraphResult(getConnection() .getStatements(valueConverter.toRdf4j(subject), (IRI) valueConverter.toRdf4j(predicate), valueConverter.toRdf4j(object), includeInferred, valueConverter.toRdf4jIRI(contexts))); injector.injectMembers(result); return result; } catch (Exception e) { throw new KommaException(e); } } @Override public IReference blankNode() { return new RDF4JReference(getConnection().getValueFactory() .createBNode()); } @Override public IReference blankNode(String id) { if (id == null) { return blankNode(); } if (id.startsWith("_:")) { id = id.substring(2); } return new RDF4JReference(getConnection().getValueFactory() .createBNode(id)); } @Override public IDataManager remove( Iterable<? extends IStatementPattern> statements, IReference... contexts) { if (contexts.length == 0) { contexts = NULL_CTX; } IRI[] removeContexts = valueConverter.toRdf4jIRI(contexts); try { RepositoryConnection conn = getConnection(); boolean trackChanges = changeSupport.isEnabled(this); for (IStatementPattern stmt : statements) { if (stmt instanceof IStatement && ((IStatement) stmt).isInferred()) { // special handling for inferred statements if (trackChanges) { for (IReference ctx : contexts) { changeSupport.remove( this, new Statement(stmt.getSubject(), stmt .getPredicate(), stmt.getObject(), ctx, true)); } } } else { Resource subject = valueConverter.toRdf4j(stmt .getSubject()); IRI predicate = (IRI) valueConverter.toRdf4j(stmt .getPredicate()); Value object = valueConverter.toRdf4j((IValue) stmt .getObject()); if (trackChanges) { for (IStatement existing : match(stmt.getSubject(), stmt.getPredicate(), (IValue) stmt.getObject(), false, contexts)) { // pretend that statement was in changeCtx if no // context // is set IReference[] changeContexts = existing.getContext() != null ? new IReference[] { existing .getContext() } : contexts; for (IReference ctx : changeContexts) { changeSupport.remove(this, new Statement(existing.getSubject(), existing.getPredicate(), existing.getObject(), ctx, existing.isInferred())); } } } conn.remove(subject, predicate, object, removeContexts); } } if (changeSupport.isEnabled(this) && !getTransaction().isActive()) { changeSupport.commit(this); } } catch (Exception e) { throw new KommaException(e); } return this; } @Override public IDataManager removeNamespace(String prefix) { try { if (changeSupport.isEnabled(this)) { String namespace = getConnection().getNamespace(prefix); if (namespace != null) { changeSupport.removeNamespace(this, prefix, URIs.createURI(namespace)); } } getConnection().removeNamespace(prefix); if (changeSupport.isEnabled(this)) { changeSupport.commit(this); } } catch (Exception e) { throw new KommaException(e); } return this; } @Override public IDataManager setNamespace(String prefix, net.enilink.komma.core.URI uri) { try { if (changeSupport.isEnabled(this)) { net.enilink.komma.core.URI existing = getNamespace(prefix); changeSupport.setNamespace(this, prefix, existing, uri); } getConnection().setNamespace(prefix, uri.toString()); if (changeSupport.isEnabled(this)) { changeSupport.commit(this); } } catch (Exception e) { throw new KommaException(e); } return this; } /** * Clear cache of generated blank nodes. */ void clearNodeMappings() { valueConverter.reset(); } }