package de.unikiel.inf.comsys.neo4j.inference;
/*
* #%L
* neo4j-sparql-extension
* %%
* Copyright (C) 2014 Niclas Hoyer
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import de.unikiel.inf.comsys.neo4j.inference.rules.Rules;
import de.unikiel.inf.comsys.neo4j.inference.rules.Rule;
import de.unikiel.inf.comsys.neo4j.SPARQLExtensionProps;
import de.unikiel.inf.comsys.neo4j.http.RDFMediaType;
import de.unikiel.inf.comsys.neo4j.http.SPARQLUpdate;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.repository.sail.SailRepositoryConnection;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.turtle.TurtleWriterFactory;
import org.semanticweb.owlapi.formats.TurtleDocumentFormat;
import org.semanticweb.owlapi.io.OWLOntologyDocumentSource;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLDocumentFormat;
/**
* A factory for query rewriters.
*
* This factory ensures that for each given repository connection there exists
* at most one query rewriter.
*/
public class QueryRewriterFactory {
private final String ontologyContext;
private static final WeakHashMap<SailRepository, QueryRewriterFactory> map
= new WeakHashMap<>();
private static final Logger logger
= Logger.getLogger(SPARQLUpdate.class.getName());
private final SailRepository rep;
private final ExecutorService executor;
private List<Rule> rules;
/**
* Implements a document source for the use with OWL-API.
*
* This class implements a OWL-API document source, that exports the
* contents of a graph in a Sesame repository. The OWL-API can't use a
* Sesame repository directly, thus the graph is serialized in a separate
* thread into Turtle and streamed to the OWL-API.
*/
private class RepositorySource implements OWLOntologyDocumentSource {
private final URI ctx;
private final IRI iri;
/**
* Creates a new repository source.
*/
public RepositorySource() {
this.ctx = rep.getValueFactory().createURI(ontologyContext);
this.iri = IRI.create(ontologyContext);
}
@Override
public boolean isReaderAvailable() {
return false;
}
@Override
public Reader getReader() {
return null;
}
@Override
public boolean isInputStreamAvailable() {
return rep.isInitialized();
}
/**
* Returns a input stream that streams a graph serialized in Turtle.
*
* @return input stream that streams the graph
*/
@Override
public InputStream getInputStream() {
if (isInputStreamAvailable()) {
// create a pipe to connect the output stream of the turtle
// serializer to the given input stream
final PipedOutputStream out;
final PipedInputStream in = new PipedInputStream(2048);
try {
out = new PipedOutputStream(in);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
// run serialization in separate thread
Runnable exporter = new Runnable() {
@Override
public void run() {
SailRepositoryConnection conn = null;
try {
conn = rep.getConnection();
TurtleWriterFactory factory
= new TurtleWriterFactory();
// export the graph as turtle
conn.export(factory.getWriter(out), ctx);
out.close();
conn.close();
} catch (RepositoryException |
RDFHandlerException |
IOException ex) {
// server error
try {
if (conn != null && conn.isOpen()) {
conn.close();
}
} catch (RepositoryException ex1) {
ex.addSuppressed(ex1);
}
// catch a specific "Pipe closed" error that
// is caused by the OWL-API, when the input stream
// is closed prematurily
if (!(ex instanceof RDFHandlerException
&& ex.getCause() instanceof IOException
&& ex.getCause().getMessage().equals("Pipe closed"))) {
logger.log(
Level.WARNING,
"Error while exporting ontology",
ex);
}
}
}
};
// run in a separate thread
executor.submit(exporter);
return in;
}
return null;
}
@Override
public IRI getDocumentIRI() {
return iri;
}
@Override
public OWLDocumentFormat getFormat() {
return new TurtleDocumentFormat();
}
@Override
public boolean isFormatKnown() {
return true;
}
@Override
public String getMIMEType() {
return RDFMediaType.RDF_TURTLE;
}
@Override
public boolean isMIMETypeKnown() {
return true;
}
}
/**
* Creates a new query rewriter factory.
*
* @param rep the repository to use
*/
private QueryRewriterFactory(SailRepository rep) {
this.ontologyContext = SPARQLExtensionProps
.getProperty("inference.graph");
this.rules = new ArrayList<>();
this.rep = rep;
this.executor = Executors.newCachedThreadPool();
// initial initialization of rewriting rules based on graph in
// repository
try {
updateOntology(rep.getConnection());
} catch (RepositoryException ex) {
throw new RuntimeException(ex);
}
}
/**
* Updates the set of rules used for query rewriting.
*
* @param conn the connection to use
*/
public final synchronized void
updateOntology(SailRepositoryConnection conn) {
try {
// reload the graph and if not empty load rules from the graph
URI ctx = conn.getValueFactory().createURI(ontologyContext);
if (conn.size(ctx) > 0) {
rules = Rules.fromOntology(new RepositorySource());
}
} catch (RepositoryException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns a query rewriter for a given repository connection.
*
* @param conn the connection to use
* @return a query rewriter that uses the TBox from the repository
*/
public synchronized QueryRewriter
getRewriter(SailRepositoryConnection conn) {
return new QueryRewriter(conn, rules);
}
/**
* Returns a new query rewriter factory instance.
*
* @param rep the repository connection to use
* @return query rewriter factory
*/
public static synchronized QueryRewriterFactory
getInstance(SailRepository rep) {
QueryRewriterFactory inst;
if (!map.containsKey(rep)) {
inst = new QueryRewriterFactory(rep);
map.put(rep, inst);
} else {
inst = map.get(rep);
}
return inst;
}
/**
* Returns the graph that is used for the TBox.
*
* @return graph as string
*/
public String getOntologyContext() {
return this.ontologyContext;
}
}