package eu.fbk.knowledgestore.datastore;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Handler;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.data.Stream;
import eu.fbk.knowledgestore.data.XPath;
import eu.fbk.knowledgestore.runtime.DataCorruptedException;
import eu.fbk.knowledgestore.triplestore.SelectQuery;
import eu.fbk.knowledgestore.triplestore.TripleStore;
import eu.fbk.knowledgestore.triplestore.TripleTransaction;
import eu.fbk.knowledgestore.vocabulary.KS;
public final class TripleDataStore implements DataStore {
private static final Logger LOGGER = LoggerFactory.getLogger(TripleDataStore.class);
private final TripleStore tripleStore;
private boolean initialized;
private boolean closed;
public TripleDataStore(final TripleStore tripleStore) {
this.tripleStore = Preconditions.checkNotNull(tripleStore);
this.initialized = false;
this.closed = false;
LOGGER.info("{} configured, triplestore={}", this, this.tripleStore);
}
@Override
public synchronized void init() throws IOException, IllegalStateException {
Preconditions.checkState(!this.initialized && !this.closed);
this.initialized = true;
LOGGER.info("{} initialized", this);
}
@Override
public synchronized DataTransaction begin(final boolean readOnly)
throws DataCorruptedException, IOException, IllegalStateException {
Preconditions.checkState(this.initialized && !this.closed);
return new TripleDataTransaction(this.tripleStore.begin(readOnly));
}
@Override
public synchronized void close() {
if (this.closed) {
return;
}
this.closed = true;
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
private static final class TripleDataTransaction implements DataTransaction {
private final TripleTransaction transaction;
TripleDataTransaction(final TripleTransaction transaction) {
this.transaction = transaction;
}
private Stream<Record> query(final String spoPattern, final URI type,
@Nullable final Set<? extends URI> properties, @Nullable final XPath condition)
throws IOException {
// Compose the query
final StringBuilder builder = new StringBuilder();
if (KS.RESOURCE.equals(type)) {
builder.append("SELECT ?s ?p ?o ?p1 ?o1 ?p2 ?o2 {\n" //
+ " ?s ?p ?o .\n" //
+ " OPTIONAL {\n ?o ?p1 ?o1\n" //
+ " FILTER (?p = <http://dkm.fbk.eu/ontologies/knowledgestore#storedAs>)\n" //
+ " OPTIONAL {\n" //
+ " ?o1 ?p2 ?o2\n" //
+ " FILTER (?p1 = <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#hasHash>)\n" //
+ " }\n" //
+ " }\n ");
} else {
builder.append("SELECT ?s ?p ?o {\n" //
+ " ?s ?p ?o .\n");
}
builder.append(" ?s a ").append(Data.toString(type, null)).append(" .\n");
builder.append(spoPattern);
builder.append("\n}");
final String query = builder.toString();
// Issue the query
final Stream<BindingSet> bindingStream = Stream.create(this.transaction.query(
SelectQuery.from(query), null, null));
// Convert from bindings to statements
final Stream<Statement> stmtStream;
if (KS.RESOURCE.equals(type)) {
stmtStream = bindingStream.transform(null,
new Function<Handler<Statement>, Handler<BindingSet>>() {
@Override
public Handler<BindingSet> apply(final Handler<Statement> handler) {
return new Handler<BindingSet>() {
private final Set<Statement> set = Sets.newHashSet();
private Resource subject = null;
@Override
public void handle(final BindingSet bindings) throws Throwable {
if (bindings == null) {
handler.handle(null);
return;
}
final Resource s = (Resource) bindings.getValue("s");
final URI p = (URI) bindings.getValue("p");
final Value o = bindings.getValue("o");
final URI p1 = (URI) bindings.getValue("p1");
final Value o1 = bindings.getValue("o1");
final URI p2 = (URI) bindings.getValue("p2");
final Value o2 = bindings.getValue("o2");
final ValueFactory vf = Data.getValueFactory();
if (!s.equals(this.subject)) {
this.set.clear();
this.subject = s;
}
emit(handler, vf.createStatement(s, p, o));
if (o1 != null) {
emit(handler, vf.createStatement((URI) o, p1, o1));
if (o2 != null) {
emit(handler, vf.createStatement((URI) o1, p2, o2));
}
}
}
private void emit(final Handler<Statement> handler,
final Statement statement) throws Throwable {
if (this.set.add(statement)) {
handler.handle(statement);
}
}
};
}
});
} else {
stmtStream = bindingStream.transform(new Function<BindingSet, Statement>() {
@Override
public Statement apply(final BindingSet bindings) {
final Resource s = (Resource) bindings.getValue("s");
final URI p = (URI) bindings.getValue("p");
final Value o = bindings.getValue("o");
return Data.getValueFactory().createStatement(s, p, o);
}
}, 1);
}
// Convert from statements to records
Stream<Record> recordStream = Record.decode(stmtStream, ImmutableList.of(type), true);
// Apply condition, if specified
if (condition != null) {
recordStream = recordStream.filter(condition.asPredicate(), 1);
}
// Apply projection, if specified
if (properties != null && !properties.isEmpty()) {
final URI[] props = properties.toArray(new URI[properties.size()]);
recordStream = recordStream.transform(new Function<Record, Record>() {
@Override
public Record apply(final Record record) {
record.retain(props);
return null;
}
}, 1);
}
return recordStream;
}
@Override
public Stream<Record> lookup(final URI type, final Set<? extends URI> ids,
final Set<? extends URI> properties) throws IOException, IllegalArgumentException,
IllegalStateException {
return Stream.concat(Stream.create(ids).chunk(64)
.transform(new Function<List<? extends URI>, Stream<Record>>() {
@Override
public Stream<Record> apply(final List<? extends URI> input) {
final StringBuilder builder = new StringBuilder();
builder.append(" VALUES ?s {");
for (final URI id : input) {
builder.append(" <").append(id.toString()).append(">");
}
builder.append(" }");
try {
return query(builder.toString(), type, properties, null);
} catch (final IOException ex) {
throw Throwables.propagate(ex);
}
}
}, 1));
}
@Override
public Stream<Record> retrieve(final URI type, final XPath condition,
final Set<? extends URI> properties) throws IOException, IllegalArgumentException,
IllegalStateException {
return query(" ?s a <" + type.toString() + "> .", type, properties, condition);
}
@Override
public long count(final URI type, final XPath condition) throws IOException,
IllegalArgumentException, IllegalStateException {
return query(" ?s a <" + type.toString() + "> .", type, null, condition).count();
}
@Override
public Stream<Record> match(final Map<URI, XPath> conditions,
final Map<URI, Set<URI>> ids, final Map<URI, Set<URI>> properties)
throws IOException, IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public void store(final URI type, final Record record) throws IOException,
IllegalStateException {
// Delete existing data for the record URI
delete(type, record.getID());
// Add statements
final List<Statement> statements = Record.encode(Stream.create(record),
ImmutableList.of(type)).toList();
this.transaction.add(statements);
}
@Override
public void delete(final URI type, final URI id) throws IOException, IllegalStateException {
// Obtain the statements to delete throuh a lookup
final List<Statement> statements = Record.encode(
lookup(type, ImmutableSet.of(id), null), ImmutableList.of(type)).toList();
// Perform the deletion
this.transaction.remove(statements);
}
@Override
public void end(final boolean commit) throws DataCorruptedException, IOException,
IllegalStateException {
this.transaction.end(commit);
}
}
}