/* * Copyright 2016 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.studios.shine.semweb.sesame; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import com.thoughtworks.studios.shine.ShineRuntimeException; import com.thoughtworks.studios.shine.semweb.BoundVariables; import com.thoughtworks.studios.shine.semweb.Graph; import com.thoughtworks.studios.shine.semweb.MalformedSPARQLException; import com.thoughtworks.studios.shine.semweb.MoreThanOneResultFoundException; import com.thoughtworks.studios.shine.semweb.Namespace; import com.thoughtworks.studios.shine.semweb.RDFOntology; import com.thoughtworks.studios.shine.semweb.RDFProperty; import com.thoughtworks.studios.shine.semweb.RDFType; import com.thoughtworks.studios.shine.semweb.Resource; import com.thoughtworks.studios.shine.semweb.URIReference; import com.thoughtworks.studios.shine.semweb.UUIDURIGenerator; import com.thoughtworks.studios.shine.semweb.UnsupportedSPARQLStatementException; import com.thoughtworks.studios.shine.util.ArgumentUtil; import org.apache.log4j.Logger; import org.openrdf.OpenRDFException; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.vocabulary.RDF; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.query.BooleanQuery; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.Query; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.QueryLanguage; import org.openrdf.query.TupleQuery; import org.openrdf.query.TupleQueryResult; import org.openrdf.query.TupleQueryResultHandlerException; import org.openrdf.query.algebra.StatementPattern; import org.openrdf.query.algebra.TupleExpr; import org.openrdf.query.algebra.Var; import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; import org.openrdf.query.resultio.BooleanQueryResultWriter; import org.openrdf.query.resultio.TupleQueryResultWriter; import org.openrdf.query.resultio.sparqlxml.SPARQLBooleanXMLWriter; import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriter; import org.openrdf.repository.Repository; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; import org.openrdf.repository.RepositoryResult; import org.openrdf.repository.sail.SailQuery; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFHandlerException; import org.openrdf.rio.n3.N3Writer; import org.openrdf.rio.rdfxml.RDFXMLWriter; public class SesameGraph implements Graph { private final RepositoryConnection conn; private org.openrdf.model.Resource[] contextResource; private Var contextVar; private List<Graph> tempGraphs = new ArrayList<>(); private final static Logger LOGGER = Logger.getLogger(SesameGraph.class); private Map<RDFProperty, URI> sesameNativeTypeByRDFProperty = new HashMap<>(); public SesameGraph(RepositoryConnection conn) { this(conn, null); } public SesameGraph(RepositoryConnection conn, String contextURI) { this.conn = conn; if (contextURI != null) { org.openrdf.model.Resource contextResource = ((SesameURIReference) getURIReference(contextURI)).getSesameNativeResource(); this.contextResource = new org.openrdf.model.Resource[]{contextResource}; contextVar = new Var("magic-context", conn.getValueFactory().createURI(contextResource.stringValue())); contextVar.setAnonymous(true); } else { this.contextResource = new org.openrdf.model.Resource[0]; } } public void addNamespace(Namespace namespace) { try { conn.setNamespace(namespace.getPrefix(), namespace.getURIText()); } catch (Exception e) { throw new ShineRuntimeException("Could not add namespace!" + namespace, e); } } public Namespace getNamespaceByPrefix(String prefix) { try { return new Namespace(prefix, conn.getNamespace(prefix)); } catch (Exception e) { throw new ShineRuntimeException("Could not get namespace by prefix" + prefix, e); } } public void addStatement(Resource subject, RDFProperty predicate, Resource object) { AbstractSesameResource sesameSubject = (AbstractSesameResource) subject; URI sesameNativePredicate = getSesameNativeProperty(predicate); AbstractSesameResource sesameObject = (AbstractSesameResource) object; try { conn.add(sesameSubject.getSesameNativeResource(), sesameNativePredicate, sesameObject.getSesameNativeResource(), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException("Could not add statement << [" + subject + "] [" + predicate + "] [" + object + "] >>", e); } } public void addStatement(Resource subject, RDFProperty predicate, Integer object) { AbstractSesameResource sesameSubject = (AbstractSesameResource) subject; URI sesameNativePredicate = getSesameNativeProperty(predicate); try { conn.add(sesameSubject.getSesameNativeResource(), sesameNativePredicate, conn.getValueFactory().createLiteral(String.valueOf(object), XMLSchema.INTEGER), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException("Could not add statement << [" + subject + "] [" + predicate + "] [" + object + "] >>", e); } } public void addStatement(Resource subject, RDFProperty predicate, String object) { AbstractSesameResource sesameSubject = (AbstractSesameResource) subject; URI sesameNativePredicate = getSesameNativeProperty(predicate); try { conn.add(sesameSubject.getSesameNativeResource(), sesameNativePredicate, conn.getValueFactory().createLiteral(String.valueOf(object), XMLSchema.STRING), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException("Could not add statement << [" + subject + "] [" + predicate + "] [" + object + "] >>", e); } } public void addStatement(Resource subject, RDFProperty predicate, Boolean object) { AbstractSesameResource sesameSubject = (AbstractSesameResource) subject; URI sesameNativePredicate = getSesameNativeProperty(predicate); try { conn.add(sesameSubject.getSesameNativeResource(), sesameNativePredicate, conn.getValueFactory().createLiteral(String.valueOf(object), XMLSchema.BOOLEAN), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException("Could not add statement << [" + subject + "] [" + predicate + "] [" + object + "] >>", e); } } URI getSesameNativeProperty(RDFProperty predicate) { if (!sesameNativeTypeByRDFProperty.containsKey(predicate)) { String predicateURIText = predicate.getURIText(); URI predicateURI = conn.getValueFactory().createURI(predicateURIText); sesameNativeTypeByRDFProperty.put(predicate, predicateURI); } return sesameNativeTypeByRDFProperty.get(predicate); } public void addTriplesFromGraph(Graph graph) { SesameGraph sesameNativeGraph = (SesameGraph) graph; try { conn.add(sesameNativeGraph.conn.getStatements(null, null, null, false), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } } public void addTriplesFromTurtle(String rdf) { try { conn.add(new StringReader(rdf), "", RDFFormat.TURTLE, contextResource); } catch (Exception e) { throw new ShineRuntimeException(e); } } public void addTriplesFromTurtle(InputStream stream) { try { conn.add(stream, "", RDFFormat.TURTLE, contextResource); } catch (Exception e) { throw new ShineRuntimeException(e); } finally { closeStream(stream); } } private void closeStream(Closeable closable) { try { closable.close(); } catch (IOException e) { throw new ShineRuntimeException(e); } } public Boolean ask(String sparqlAsk) { return getBooleanQueryResult(sparqlAsk); } public void clearAllTriples() { try { conn.clear(); } catch (Exception e) { throw new ShineRuntimeException(e); } } public void close() { try { for (Graph tempGraph : tempGraphs) { tempGraph.close(); } tempGraphs.clear(); conn.commit(); conn.close(); } catch (RepositoryException e) { throw new ShineRuntimeException("Could not close graph!", e); } } public URIReference createFakeBlankNode(RDFType type) { return createURIReference(type, UUIDURIGenerator.nextType4()); } public Graph createTempGraph() { try { if (!conn.isOpen()) { throw new IllegalStateException("Cannot create a temp graph on a closed graph!"); } Repository inMemRepos = InMemoryRepositoryFactory.emptyRepository(); String contextURI = null; if (contextResource.length > 0) { contextURI = contextResource[0].stringValue(); } Graph tempGraph = new SesameGraph(inMemRepos.getConnection(), contextURI); tempGraphs.add(tempGraph); return tempGraph; } catch (RepositoryException ex) { throw new ShineRuntimeException("Unable to create temp graph!", ex); } } public void addTriplesFromRDFXMLAbbrev(Reader reader) { try { conn.add(reader, "", RDFFormat.RDFXML, contextResource); } catch (Exception e) { throw new ShineRuntimeException("Could not create graph from XML RDF!", e); } finally { closeStream(reader); } } public URIReference createURIReference(RDFType type, String uri) { ArgumentUtil.guaranteeNotNull(type, "Type may not be null."); ArgumentUtil.guaranteeNotNull(uri, "URI may not be null."); ArgumentUtil.guaranteeFalse("URI may not be a blank node!", uri.startsWith("_:")); URI sesameNativeURI = conn.getValueFactory().createURI(uri); try { conn.add(sesameNativeURI, RDF.TYPE, conn.getValueFactory().createURI(type.getURIText()), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } return new SesameURIReference(sesameNativeURI); } public URIReference getURIReference(String uri) { // TODO: JS - I'm not sure that this is a good idea. Is there a way to do this without "creating" the uri? return new SesameURIReference(conn.getValueFactory().createURI(uri)); } org.openrdf.model.Value getPropertyValue(org.openrdf.model.Resource resource, RDFProperty rdfProperty) { try { return conn.getStatements(resource, getSesameNativeProperty(rdfProperty), null, false).next().getObject(); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } catch (NoSuchElementException e) { return null; } } public void renderSPARQLResultsAsXML(String sparql, OutputStream stream) { try { Query query = conn.prepareQuery(QueryLanguage.SPARQL, sparql); contextualize(query); if (query instanceof TupleQuery) { renderTupleQuery(query, new SPARQLResultsXMLWriter(stream)); } else { renderBooleanQuery(query, new SPARQLBooleanXMLWriter(stream)); } stream.flush(); } catch (UnsupportedSPARQLStatementException e) { throw e; } catch (Exception e) { throw new ShineRuntimeException("Could not render sparql results as XML: <<" + sparql + ">>", e); } } private void renderBooleanQuery(Query query, BooleanQueryResultWriter writer) throws IOException, QueryEvaluationException { writer.write(((BooleanQuery) query).evaluate()); } private void renderTupleQuery(Query query, TupleQueryResultWriter writer) throws QueryEvaluationException, TupleQueryResultHandlerException { TupleQueryResult tupleQueryResult = ((TupleQuery) query).evaluate(); writer.startQueryResult(tupleQueryResult.getBindingNames()); while (tupleQueryResult.hasNext()) { writer.handleSolution(tupleQueryResult.next()); } writer.endQueryResult(); } public void remove(Resource tripleSubject, RDFProperty triplePredicate, Resource tripleObject) { AbstractSesameResource sesameSubject = (AbstractSesameResource) tripleSubject; URI sesameNativePredicate = getSesameNativeProperty(triplePredicate); AbstractSesameResource sesameObject = (AbstractSesameResource) tripleObject; try { conn.remove(sesameSubject.getSesameNativeResource(), sesameNativePredicate, sesameObject.getSesameNativeResource(), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } } public void remove(Resource tripleSubject, RDFProperty triplePredicate, Integer integer) { AbstractSesameResource sesameSubject = (AbstractSesameResource) tripleSubject; URI sesameNativePredicate = getSesameNativeProperty(triplePredicate); try { conn.remove(sesameSubject.getSesameNativeResource(), sesameNativePredicate, conn.getValueFactory().createLiteral(String.valueOf(integer), XMLSchema.INTEGER), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } } public void removeTypeOn(Resource tripleSubject, RDFType tripleObject) { remove(tripleSubject, RDFOntology.TYPE, getURIReference(tripleObject.getURIText())); } public void remove(Resource tripleSubject, RDFProperty triplePredicate, String tripleObject) { AbstractSesameResource sesameSubject = (AbstractSesameResource) tripleSubject; URI sesameNativePredicate = getSesameNativeProperty(triplePredicate); try { conn.remove(sesameSubject.getSesameNativeResource(), sesameNativePredicate, conn.getValueFactory().createLiteral(tripleObject, XMLSchema.STRING), contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } } public List<BoundVariables> select(String sparqlSelect) { List<BoundVariables> results = new LinkedList<>(); TupleQueryResult tupleQueryResult = getTupleQueryResult(sparqlSelect); try { while (tupleQueryResult.hasNext()) { results.add(new SesameBoundVariables(tupleQueryResult.getBindingNames(), tupleQueryResult.next())); } } catch (QueryEvaluationException e) { throw new ShineRuntimeException(e); } return results; } private TupleQueryResult getTupleQueryResult(String sparqlSelect) { try { TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, sparqlSelect); contextualize(tupleQuery); return tupleQuery.evaluate(); } catch (UnsupportedSPARQLStatementException e) { throw e; } catch (Exception e) { throw new ShineRuntimeException(e); } } private void contextualize(Query query) throws Exception { if (contextVar == null) { return; } TupleExpr tupleExpr = ((SailQuery) query).getParsedQuery().getTupleExpr(); tupleExpr.visit(new QueryModelVisitorBase() { public void meet(StatementPattern node) throws Exception { if (node.getContextVar() != null) { throw new UnsupportedSPARQLStatementException("Attempted to execute a SPARQL statement with a GRAPH clause against a context aware graph."); } node.setContextVar(contextVar); } }); } private boolean getBooleanQueryResult(String sparqlSelect) { try { BooleanQuery booleanQuery = conn.prepareBooleanQuery(QueryLanguage.SPARQL, sparqlSelect); contextualize(booleanQuery); return booleanQuery.evaluate(); } catch (UnsupportedSPARQLStatementException e) { throw e; } catch (Exception e) { throw new ShineRuntimeException(e); } } public BoundVariables selectFirst(String sparqlSelect) { BoundVariables boundVariables; TupleQueryResult tupleQueryResult = getTupleQueryResult(sparqlSelect); try { if (!tupleQueryResult.hasNext()) { return null; } boundVariables = new SesameBoundVariables(tupleQueryResult.getBindingNames(), tupleQueryResult.next()); if (tupleQueryResult.hasNext()) { tupleQueryResult.close(); throw new MoreThanOneResultFoundException(sparqlSelect); } } catch (QueryEvaluationException e) { throw new ShineRuntimeException("Could not parse query: <<" + sparqlSelect + ">>", e); } return boundVariables; } public long size() { try { return conn.size(contextResource); } catch (RepositoryException e) { throw new ShineRuntimeException(e); } } public void dump(Writer writer) { try { if (contextResource.length == 0) { RepositoryResult<org.openrdf.model.Resource> results = conn.getContextIDs(); while (results.hasNext()) { org.openrdf.model.Resource context = results.next(); writer.append("Dumping context:" + context + "\n"); conn.export(new RDFXMLWriter(writer), context); } dumpTriplesNotInContext(writer); } else { for (int i = 0; i < contextResource.length; i++) { writer.append("Dumping context:" + contextResource[i].stringValue() + "\n"); conn.export(new RDFXMLWriter(writer), contextResource); } } } catch (Exception e) { throw new ShineRuntimeException(e); } } public void validate(String arq) { try { Query query = conn.prepareQuery(QueryLanguage.SPARQL, arq); contextualize(query); } catch (UnsupportedSPARQLStatementException e) { throw e; } catch (MalformedQueryException e) { throw new MalformedSPARQLException(e); } catch (Exception e) { throw new ShineRuntimeException(e); } } public void persistToTurtle(OutputStream outputStream) { try { conn.export(new N3Writer(outputStream)); } catch (OpenRDFException e) { throw new ShineRuntimeException(e); } } private void dumpTriplesNotInContext(Writer writer) { try { writer.append("Statements not in any context: \n"); } catch (IOException e) { throw new RuntimeException(e); } RDFXMLWriter xmlWriter = new RDFXMLWriter(writer); xmlWriter.startRDF(); try { RepositoryResult<Statement> result = conn.getStatements(null, null, null, false); while (result.hasNext()) { Statement statement = result.next(); if (statement.getContext() == null) { xmlWriter.handleStatement(statement); } } } catch (RepositoryException | RDFHandlerException e) { throw new RuntimeException(e); } finally { try { xmlWriter.endRDF(); } catch (RDFHandlerException e) { throw new RuntimeException(e); } } } public boolean containsResourceWithURI(String URI) { return ask("ASK WHERE { <" + URI + "> ?p ?o . }"); } public String toString() { if (contextResource.length == 0) { return "Graph bound to default context."; } return "Graph bound to " + contextResource[0]; } }