/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.inferencer.fc;
import java.util.Collection;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.openrdf.cursor.Cursor;
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.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.model.vocabulary.SESAME;
import org.openrdf.query.BindingSet;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.impl.EmptyBindingSet;
import org.openrdf.query.parser.GraphQueryModel;
import org.openrdf.query.parser.QueryParserUtil;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.sail.NotifyingSail;
import org.openrdf.sail.SailConnectionListener;
import org.openrdf.sail.SailMetaData;
import org.openrdf.sail.helpers.NotifyingSailWrapper;
import org.openrdf.sail.helpers.SailMetaDataWrapper;
import org.openrdf.sail.inferencer.InferencerConnection;
import org.openrdf.sail.inferencer.fc.config.DirectTypeHierarchyInferencerFactory;
import org.openrdf.sail.inferencer.helpers.AutoCommitInferencerConnection;
import org.openrdf.sail.inferencer.helpers.InferencerConnectionWrapper;
import org.openrdf.store.StoreException;
/**
* A forward-chaining inferencer that infers the direct-type hierarchy relations
* {@link SESAME#DIRECTSUBCLASSOF sesame:directSubClassOf},
* {@link SESAME#DIRECTSUBPROPERTYOF sesame:directSubPropertyOf} and
* {@link SESAME#DIRECTTYPE sesame:directType}.
* <p>
* The semantics of this inferencer are defined as follows:
*
* <pre>
* Class A is a direct subclass of B iff:
* 1. A is a subclass of B and;
* 2. A and B are not equa and;
* 3. there is no class C (unequal A and B) such that
* A is a subclass of C and C of B.
*
* Property P is a direct subproperty of Q iff:
* 1. P is a subproperty of Q and;
* 2. P and Q are not equal and;
* 3. there is no property R (unequal P and Q) such that
* P is a subproperty of R and R of Q.
*
* Resource I is of direct type T iff:
* 1. I is of type T and
* 2. There is no class U (unequal T) such that:
* a. U is a subclass of T and;
* b. I is of type U.
* </pre>
*/
public class DirectTypeHierarchyInferencer extends NotifyingSailWrapper {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/*-----------*
* Constants *
*-----------*/
private static final GraphQueryModel DIRECT_SUBCLASSOF_MATCHER;
private static final GraphQueryModel DIRECT_SUBCLASSOF_QUERY;
private static final GraphQueryModel DIRECT_SUBPROPERTYOF_MATCHER;
private static final GraphQueryModel DIRECT_SUBPROPERTYOF_QUERY;
private static final GraphQueryModel DIRECT_TYPE_MATCHER;
private static final GraphQueryModel DIRECT_TYPE_QUERY;
static {
DIRECT_SUBCLASSOF_MATCHER = QueryParserUtil.parseGraphQuery(QueryLanguage.SERQL,
"CONSTRUCT * FROM {X} sesame:directSubClassOf {Y} ", null);
DIRECT_SUBPROPERTYOF_MATCHER = QueryParserUtil.parseGraphQuery(QueryLanguage.SERQL,
"CONSTRUCT * FROM {X} sesame:directType {Y}", null);
DIRECT_TYPE_MATCHER = QueryParserUtil.parseGraphQuery(QueryLanguage.SERQL,
"CONSTRUCT * FROM {X} sesame:directSubPropertyOf {Y}", null);
DIRECT_SUBCLASSOF_QUERY = QueryParserUtil.parseGraphQuery(
QueryLanguage.SERQL,
"CONSTRUCT {X} sesame:directSubClassOf {Y} "
+ "FROM {X} rdfs:subClassOf {Y} "
+ "WHERE X != Y AND "
+ "NOT EXISTS (SELECT Z FROM {X} rdfs:subClassOf {Z} rdfs:subClassOf {Y} WHERE X != Z AND Z != Y)",
null);
DIRECT_SUBPROPERTYOF_QUERY = QueryParserUtil.parseGraphQuery(
QueryLanguage.SERQL,
"CONSTRUCT {X} sesame:directSubPropertyOf {Y} "
+ "FROM {X} rdfs:subPropertyOf {Y} "
+ "WHERE X != Y AND "
+ "NOT EXISTS (SELECT Z FROM {X} rdfs:subPropertyOf {Z} rdfs:subPropertyOf {Y} WHERE X != Z AND Z != Y)",
null);
DIRECT_TYPE_QUERY = QueryParserUtil.parseGraphQuery(QueryLanguage.SERQL,
"CONSTRUCT {X} sesame:directType {Y} FROM {X} rdf:type {Y} "
+ "WHERE NOT EXISTS (SELECT Z FROM {X} rdf:type {Z} rdfs:subClassOf {Y} WHERE Z != Y)",
null);
}
/*--------------*
* Constructors *
*--------------*/
public DirectTypeHierarchyInferencer() {
super();
}
public DirectTypeHierarchyInferencer(NotifyingSail baseSail) {
super(baseSail);
}
/*---------*
* Methods *
*---------*/
@Override
public InferencerConnection getConnection()
throws StoreException
{
try {
InferencerConnection con = (InferencerConnection)super.getConnection();
con = new DirectTypeHierarchyInferencerConnection(con);
con = new AutoCommitInferencerConnection(con);
return con;
}
catch (ClassCastException e) {
throw new StoreException(e.getMessage(), e);
}
}
@Override
public void initialize()
throws StoreException
{
super.initialize();
InferencerConnection con = getConnection();
try {
con.flushUpdates();
}
finally {
con.close();
}
}
@Override
public SailMetaData getMetaData()
throws StoreException
{
return new SailMetaDataWrapper(super.getMetaData()) {
@Override
public String[] getReasoners() {
String[] reasoners = super.getReasoners();
String[] result = new String[reasoners.length + 1];
result[reasoners.length] = DirectTypeHierarchyInferencerFactory.SAIL_TYPE;
return result;
}
@Override
public boolean isInferencing() {
return true;
}
@Override
public String[] getInferenceRules() {
String[] rules = super.getInferenceRules();
String[] result = new String[rules.length + 6];
result[rules.length + 0] = "DIRECT_SUBCLASSOF_MATCHER";
result[rules.length + 1] = "DIRECT_SUBCLASSOF_QUERY";
result[rules.length + 2] = "DIRECT_SUBPROPERTYOF_MATCHER";
result[rules.length + 3] = "DIRECT_SUBPROPERTYOF_QUERY";
result[rules.length + 4] = "DIRECT_TYPE_MATCHER";
result[rules.length + 5] = "DIRECT_TYPE_QUERY";
return result;
}
};
}
/*-----------------------------------------------------*
* Inner class DirectTypeHierarchyInferencerConnection *
*-----------------------------------------------------*/
private class DirectTypeHierarchyInferencerConnection extends InferencerConnectionWrapper implements
SailConnectionListener
{
/**
* Flag indicating whether an update of the inferred statements is needed.
*/
private boolean updateNeeded = false;
public DirectTypeHierarchyInferencerConnection(InferencerConnection con) {
super(con);
con.addConnectionListener(this);
}
// called by base sail
public void statementAdded(Statement st) {
checkUpdatedStatement(st);
}
// called by base sail
public void statementRemoved(Statement st) {
checkUpdatedStatement(st);
}
private void checkUpdatedStatement(Statement st) {
URI pred = st.getPredicate();
if (pred.equals(RDF.TYPE) || pred.equals(RDFS.SUBCLASSOF) || pred.equals(RDFS.SUBPROPERTYOF)) {
updateNeeded = true;
}
}
@Override
public void rollback()
throws StoreException
{
super.rollback();
updateNeeded = false;
}
@Override
public void flushUpdates()
throws StoreException
{
super.flushUpdates();
if (!updateNeeded) {
return;
}
try {
// Determine which statements should be added and which should be
// removed
Collection<Statement> oldStatements = new HashSet<Statement>(256);
Collection<Statement> newStatements = new HashSet<Statement>(256);
evaluateIntoStatements(DIRECT_SUBCLASSOF_MATCHER, oldStatements);
evaluateIntoStatements(DIRECT_SUBPROPERTYOF_MATCHER, oldStatements);
evaluateIntoStatements(DIRECT_TYPE_MATCHER, oldStatements);
evaluateIntoStatements(DIRECT_SUBCLASSOF_QUERY, newStatements);
evaluateIntoStatements(DIRECT_SUBPROPERTYOF_QUERY, newStatements);
evaluateIntoStatements(DIRECT_TYPE_QUERY, newStatements);
logger.debug("existing virtual properties: {}", oldStatements.size());
logger.debug("new virtual properties: {}", newStatements.size());
// Remove the statements that should be retained from both sets
Collection<Statement> unchangedStatements = new HashSet<Statement>(oldStatements);
unchangedStatements.retainAll(newStatements);
oldStatements.removeAll(unchangedStatements);
newStatements.removeAll(unchangedStatements);
logger.debug("virtual properties to remove: {}", oldStatements.size());
logger.debug("virtual properties to add: {}", newStatements.size());
Resource[] contexts = new Resource[] { null };
for (Statement st : oldStatements) {
removeInferredStatements(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
}
for (Statement st : newStatements) {
addInferredStatement(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
}
updateNeeded = false;
}
catch (RDFHandlerException e) {
Throwable t = e.getCause();
if (t instanceof StoreException) {
throw (StoreException)t;
}
else {
throw new StoreException(t);
}
}
}
private void evaluateIntoStatements(GraphQueryModel query, Collection<Statement> statements)
throws StoreException, RDFHandlerException, StoreException
{
Cursor<? extends BindingSet> bindingsIter = getDelegate().evaluate(query,
EmptyBindingSet.getInstance(), true);
try {
ValueFactory vf = getValueFactory();
BindingSet bindings;
while ((bindings = bindingsIter.next()) != null) {
Value subj = bindings.getValue("subject");
Value pred = bindings.getValue("predicate");
Value obj = bindings.getValue("object");
if (subj instanceof Resource && pred instanceof URI && obj != null) {
statements.add(vf.createStatement((Resource)subj, (URI)pred, obj));
}
}
}
finally {
bindingsIter.close();
}
}
} // end inner class DirectTypeHierarchyInferencerConnection
}