edu.cornell.mannlib.vitro.webapp.reasoner.SimpleReasoner.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.mannlib.vitro.webapp.reasoner.SimpleReasoner.java

Source

/* $This file is distributed under the terms of the license in /doc/license.txt$ */

package edu.cornell.mannlib.vitro.webapp.reasoner;

import static edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel.WORKING;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.hp.hpl.jena.ontology.AnnotationProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.DatasetFactory;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.Lock;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;

import edu.cornell.mannlib.vitro.webapp.dao.jena.ABoxJenaChangeListener;
import edu.cornell.mannlib.vitro.webapp.dao.jena.CumulativeDeltaModeler;
import edu.cornell.mannlib.vitro.webapp.dao.jena.DifferenceGraph;
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceGraph;
import edu.cornell.mannlib.vitro.webapp.dao.jena.event.BulkUpdateEvent;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
import edu.cornell.mannlib.vitro.webapp.rdfservice.adapters.VitroModelFactory;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;

/**
 * Allows for real-time incremental materialization or retraction of RDFS-
 * style class and property subsumption based ABox inferences as statements
 * are added to or removed from the (ABox or TBox) knowledge base. 
 * @author sjm222
 */

public class SimpleReasoner extends StatementListener {

    private static final Log log = LogFactory.getLog(SimpleReasoner.class);

    private final SearchIndexer searchIndexer;

    private OntModel tboxModel; // asserted and inferred TBox axioms
    private OntModel aboxModel; // ABox assertions
    private Model inferenceModel; // ABox inferences
    private OntModel fullModel; // contains at least the 
                                // asserted and inferred ABox

    private static final String mostSpecificTypePropertyURI = "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#mostSpecificType";
    private static final AnnotationProperty mostSpecificType = (VitroModelFactory.createOntologyModel())
            .createAnnotationProperty(mostSpecificTypePropertyURI);

    // DeltaComputer
    private CumulativeDeltaModeler aBoxDeltaModeler1 = null;
    private CumulativeDeltaModeler aBoxDeltaModeler2 = null;
    private int batchMode = 0; // values: 0, 1 and 2

    // Recomputer
    private ABoxRecomputer recomputer = null;

    private boolean stopRequested = false;

    private List<ReasonerPlugin> pluginList = new CopyOnWriteArrayList<ReasonerPlugin>();

    private boolean doSameAs = true;

    /**
     * @param tboxModel - input.  This model contains both asserted and inferred TBox axioms
     * @param aboxModel - input.  This model contains asserted ABox statements
     * @param inferenceModel - output. This is the model in which inferred (materialized) 
      *  ABox statements are maintained (added or retracted).
     * @param inferenceRebuildModel - output. This the model is temporarily used when the 
      *  whole ABox inference model is rebuilt
     * @param inferenceScratchpadModel - output. This the model is temporarily used when 
      *  the whole ABox inference model is rebuilt
      * @param searchIndexer - output. If not null, the indexer will be paused before the 
      *  ABox inference model is rebuilt and unpaused when the rebuild is complete.
      */
    public SimpleReasoner(OntModel tboxModel, RDFService rdfService, Model inferenceModel,
            Model inferenceRebuildModel, Model scratchpadModel, SearchIndexer searchIndexer) {

        this.searchIndexer = searchIndexer;

        this.tboxModel = tboxModel;

        this.fullModel = VitroModelFactory
                .createOntologyModel(VitroModelFactory.createModelForGraph(new RDFServiceGraph(rdfService)));

        this.aboxModel = VitroModelFactory.createOntologyModel(VitroModelFactory.createModelForGraph(
                new DifferenceGraph(new DifferenceGraph(new RDFServiceGraph(rdfService), inferenceModel.getGraph()),
                        tboxModel.getGraph())));

        this.inferenceModel = inferenceModel;
        this.batchMode = 0;
        aBoxDeltaModeler1 = new CumulativeDeltaModeler();
        aBoxDeltaModeler2 = new CumulativeDeltaModeler();
        recomputer = new ABoxRecomputer(tboxModel, aboxModel, rdfService, this, searchIndexer);
        stopRequested = false;

        if (rdfService == null) {
            aboxModel.register(this);
        } else {
            try {
                rdfService.registerListener(new ABoxJenaChangeListener(this));
            } catch (RDFServiceException e) {
                throw new RuntimeException("Unable to register change listener", e);
            }
        }
    }

    /**
     * This constructor is used for the unit tests only
     * 
     * @param tboxModel - input.  This model contains both asserted and inferred TBox axioms
     * @param aboxModel - input.  This model contains asserted ABox statements
     * @param inferenceModel - output. This is the model in which inferred (materialized)
      *  ABox statements are maintained (added or retracted).
      */
    public SimpleReasoner(OntModel tboxModel, OntModel aboxModel, Model inferenceModel) {
        this.searchIndexer = null;
        this.tboxModel = tboxModel;
        this.aboxModel = aboxModel;
        this.inferenceModel = inferenceModel;
        this.fullModel = VitroModelFactory.createUnion(aboxModel,
                VitroModelFactory.createOntologyModel(inferenceModel));
        aBoxDeltaModeler1 = new CumulativeDeltaModeler();
        aBoxDeltaModeler2 = new CumulativeDeltaModeler();
        this.batchMode = 0;
        stopRequested = false;
        Dataset ds = DatasetFactory.createMem();
        ds.addNamedModel(ModelNames.ABOX_ASSERTIONS, aboxModel);
        ds.addNamedModel(ModelNames.ABOX_INFERENCES, inferenceModel);
        ds.addNamedModel(ModelNames.TBOX_ASSERTIONS, tboxModel);

        ds.setDefaultModel(ModelFactory.createUnion(fullModel, tboxModel));
        recomputer = new ABoxRecomputer(tboxModel, aboxModel, new RDFServiceModel(ds), this, searchIndexer);
    }

    public void setPluginList(List<ReasonerPlugin> pluginList) {
        this.pluginList = pluginList;
    }

    public List<ReasonerPlugin> getPluginList() {
        return this.pluginList;
    }

    public void setSameAsEnabled(boolean tf) {
        this.doSameAs = tf;
    }

    public boolean getSameAsEnabled() {
        return this.doSameAs;
    }

    /*
     * Performs incremental ABox reasoning based
     * on the addition of a new statement
     *  (aka assertion) to the ABox.
     */
    @Override
    public void addedStatement(Statement stmt) {
        try {
            if (stmt.getPredicate().equals(RDF.type)) {
                addedABoxTypeAssertion(stmt, inferenceModel, new HashSet<String>());
                setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>());
            } else if (doSameAs && stmt.getPredicate().equals(OWL.sameAs)) {
                addedABoxSameAsAssertion(stmt, inferenceModel);
            } else {
                addedABoxAssertion(stmt, inferenceModel);
            }

            doPlugins(ModelUpdate.Operation.ADD, stmt);

        } catch (Exception e) { // don't stop the edit if there's an exception
            log.error("Exception while computing inferences: " + e.getMessage());
        }
    }

    /*
     * Performs incremental ABox reasoning based
     * on the retraction of a statement (aka assertion)
     * from the ABox. 
     */
    @Override
    public void removedStatement(Statement stmt) {
        try {
            handleRemovedStatement(stmt);
        } catch (Exception e) { // don't stop the edit if there's an exception
            log.error("Exception while retracting inferences: ", e);
        }
    }

    /*
     * Synchronized part of removedStatement. Interacts with DeltaComputer.
     */
    protected synchronized void handleRemovedStatement(Statement stmt) {
        if (batchMode == 1) {
            aBoxDeltaModeler1.removedStatement(stmt);
        } else if (batchMode == 2) {
            aBoxDeltaModeler2.removedStatement(stmt);
        } else { // batchMode == 0
            if (stmt.getPredicate().equals(RDF.type)) {
                removedABoxTypeAssertion(stmt, inferenceModel);
                setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>());
            } else if (doSameAs && stmt.getPredicate().equals(OWL.sameAs)) {
                removedABoxSameAsAssertion(stmt, inferenceModel);
            } else {
                removedABoxAssertion(stmt, inferenceModel);
            }
            doPlugins(ModelUpdate.Operation.RETRACT, stmt);
        }
    }

    /**
    * Performs incremental ABox reasoning based
    * on changes to the class hierarchy.
     *
     * addedTBoxStatement and removedTBoxStatement use the
     * same tests so the are merged into this method.
     *
    * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf 
    */
    protected void changedTBoxStatement(Statement stmt, boolean add) {
        try {
            if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass)
                    || stmt.getPredicate().equals(OWL.inverseOf))) {
                return;
            }

            if (!stmt.getObject().isResource()) {
                log.warn("The object of this assertion is not a resource: " + stmtString(stmt));
                return;
            }

            if (stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass)) {

                // ignore anonymous classes
                if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
                    return;
                }

                OntClass subject, object;
                tboxModel.enterCriticalSection(Lock.READ);
                try {
                    subject = tboxModel.getOntClass((stmt.getSubject()).getURI());
                    if (subject == null) {
                        log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI());
                        return;
                    }

                    object = tboxModel.getOntClass(((Resource) stmt.getObject()).getURI());
                    if (object == null) {
                        log.debug(
                                "didn't find object class in the tbox: " + ((Resource) stmt.getObject()).getURI());
                        return;
                    }
                } finally {
                    tboxModel.leaveCriticalSection();
                }

                if (stmt.getPredicate().equals(RDFS.subClassOf)) {
                    if (add) {
                        addedSubClass(subject, object, inferenceModel);
                    } else {
                        removedSubClass(subject, object, inferenceModel);
                    }
                } else {
                    // equivalent class is the same as subclass in both directions
                    if (add) {
                        addedSubClass(subject, object, inferenceModel);
                        addedSubClass(object, subject, inferenceModel);
                    } else {
                        removedSubClass(subject, object, inferenceModel);
                        removedSubClass(object, subject, inferenceModel);
                    }
                }
            } else {
                if (stmt.getObject().asResource().getURI() == null) {
                    log.warn("The object of this assertion has a null URI: " + stmtString(stmt));
                    return;
                }

                if (stmt.getSubject().getURI() == null) {
                    log.warn("The subject of this assertion has a null URI: " + stmtString(stmt));
                    return;
                }

                OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI());
                if (prop1 == null) {
                    log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI());
                    return;
                }

                OntProperty prop2 = tboxModel.getOntProperty(((Resource) stmt.getObject()).getURI());
                if (prop2 == null) {
                    log.debug("didn't find object property in the tbox: " + ((Resource) stmt.getObject()).getURI());
                    return;
                }

                if (add) {
                    addedInverseProperty(prop1, prop2, inferenceModel);
                } else {
                    removedInverseProperty(prop1, prop2, inferenceModel);
                }
            }
        } catch (Exception e) { // don't stop the edit if there's an exception
            log.error("Exception while " + (add ? "adding" : "removing") + " inference(s)", e);
        }
    }

    /**
     * Performs incremental ABox reasoning based
     * on changes to the class hierarchy.
     * 
     * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf
     */
    public void addedTBoxStatement(Statement stmt) {
        changedTBoxStatement(stmt, true);
    }

    /**
     * Performs incremental ABox reasoning based
     * on changes to the class hierarchy.
     * 
     * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf 
     */
    public void removedTBoxStatement(Statement stmt) {
        changedTBoxStatement(stmt, false);
    }

    protected void addedABoxTypeAssertion(Statement stmt, Model inferenceModel, HashSet<String> unknownTypes) {
        addedABoxTypeAssertion(stmt, inferenceModel, unknownTypes, true);
    }

    /**
     * Performs incremental reasoning based on a new type assertion
     * added to the ABox (assertion that an individual is of a certain
     * type).
     * 
     * If it is added that B is of type A, then for each superclass of
     * A assert that B is of that type.
     */
    protected void addedABoxTypeAssertion(Statement stmt, Model inferenceModel, HashSet<String> unknownTypes,
            boolean checkRedundancy) {

        tboxModel.enterCriticalSection(Lock.READ);
        try {
            Resource cls = null;
            if ((stmt.getObject().asResource()).getURI() != null) {

                cls = tboxModel.getResource(stmt.getObject().asResource().getURI());
                if (cls != null) {
                    List<Resource> parents = getParents(cls, tboxModel);

                    Iterator<Resource> parentIt = parents.iterator();

                    if (parentIt.hasNext()) {
                        while (parentIt.hasNext()) {
                            Resource parentClass = parentIt.next();

                            // VIVO doesn't materialize statements that assert anonymous types
                            // for individuals. Also, sharing an identical anonymous node is
                            // not allowed in owl-dl. picklist population code looks at qualities
                            // of classes not individuals.
                            if (parentClass.isAnon())
                                continue;

                            Statement infStmt = ResourceFactory.createStatement(stmt.getSubject(), RDF.type,
                                    parentClass);
                            addInference(infStmt, inferenceModel, true, checkRedundancy);
                        }
                    }
                } else {
                    if (!(stmt.getObject().asResource().getNameSpace()).equals(OWL.NS)) {
                        if (!unknownTypes.contains(stmt.getObject().asResource().getURI())) {
                            unknownTypes.add(stmt.getObject().asResource().getURI());
                            log.warn("Didn't find the target class (the object of an added "
                                    + "rdf:type statement) in the TBox: " + (stmt.getObject().asResource()).getURI()
                                    + ". No class subsumption reasoning will be done "
                                    + "based on type assertions of this type.");
                        }
                    }
                }
            } else {
                log.debug("The object of this rdf:type assertion has a null URI, no reasoning"
                        + " will be done based on this assertion: " + stmtString(stmt));
                return;
            }
        } finally {
            tboxModel.leaveCriticalSection();
        }

        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            if (inferenceModel.contains(stmt)) {
                inferenceModel.remove(stmt);
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }
    }

    /**
      * If it is removed that B is of type A, then for each superclass of A remove
      * the inferred statement that B is of that type UNLESS it is otherwise entailed
      * that B is of that type.
      * 
      */
    protected void removedABoxTypeAssertion(Statement stmt, Model inferenceModel) {
        removedABoxTypeAssertion(stmt, inferenceModel, null);
    }

    /**
     * If it is removed that B is of type A, then for each superclass of A remove
     * the inferred statement that B is of that type UNLESS it is otherwise entailed
     * that B is of that type.
     * 
     * remainingTypeURIs is an optional list of asserted type URIs for the subject of 
     * stmt, and may be null.  Supplying a precompiled list can yield performance 
      * improvement when this method is called repeatedly for the same subject.  
     * 
     */
    protected void removedABoxTypeAssertion(Statement stmt, Model inferenceModel, List<String> remainingTypeURIs) {
        tboxModel.enterCriticalSection(Lock.READ);
        try {
            Resource cls = null;

            if ((stmt.getObject().asResource()).getURI() != null) {
                cls = tboxModel.getResource(stmt.getObject().asResource().getURI());

                if (cls != null) {
                    if (entailedType(stmt.getSubject(), cls)) {
                        addInference(stmt, inferenceModel, true);
                    }

                    List<Resource> parents = getParents(cls, tboxModel);

                    Iterator<Resource> parentIt = parents.iterator();

                    while (parentIt.hasNext()) {

                        Resource parentClass = parentIt.next();

                        // VIVO doesn't materialize statements that assert anonymous types
                        // for individuals. Also, sharing an identical anonymous node is
                        // not allowed in owl-dl. picklist population code looks at qualities
                        // of classes not individuals.
                        if (parentClass.isAnon())
                            continue;

                        List<String> typeURIs = (remainingTypeURIs == null)
                                ? getRemainingAssertedTypeURIs(stmt.getSubject())
                                : remainingTypeURIs;
                        if (entailedType(stmt.getSubject(), parentClass, typeURIs)) {
                            continue; // if a type is still entailed without the
                        }
                        // removed statement, then don't remove it
                        // from the inferences

                        Statement infStmt = ResourceFactory.createStatement(stmt.getSubject(), RDF.type,
                                parentClass);
                        removeInference(infStmt, inferenceModel, true, false);
                    }
                } else {
                    log.warn("Didn't find target class (the object of the removed rdf:type"
                            + "statement) in the TBox: " + ((Resource) stmt.getObject()).getURI()
                            + ". No class subsumption"
                            + " reasoning will be performed based on the removal of this assertion.");
                }
            } else {
                log.warn("The object of this rdf:type assertion has a null URI: " + stmtString(stmt));
            }
        } catch (Exception e) {
            log.warn("exception while removing abox type assertions: " + e.getMessage());
        } finally {
            tboxModel.leaveCriticalSection();
        }
    }

    /**
     * Performs incremental property-based reasoning.
     * 
     * Retracts inferences based on the owl:inverseOf relationship.
     * 
     * If it is removed that x prop1 y, and prop2 is an inverseOf prop1
     * then remove y prop2 x from the inference graph, unless it is 
     * otherwise entailed by the assertions graph independently of
     * this removed statement.
     */
    protected void removedABoxAssertion(Statement stmt, Model inferenceModel) {

        if (!stmt.getObject().isLiteral()) {
            List<OntProperty> inverseProperties = getInverseProperties(stmt);
            Iterator<OntProperty> inverseIter = inverseProperties.iterator();

            while (inverseIter.hasNext()) {
                OntProperty inverseProp = inverseIter.next();
                Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp,
                        stmt.getSubject());
                removeInference(infStmt, inferenceModel);
            }
        }

        if (doSameAs)
            doSameAsForRemovedABoxAssertion(stmt, inferenceModel);

        // if a statement has been removed that is otherwise entailed,
        // add it to the inference graph.
        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            if (entailedStatement(stmt) && !inferenceModel.contains(stmt)) {
                inferenceModel.add(stmt);
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }
    }

    /**
     * If it is added that B is a subClass of A, then for each
     * individual that is typed as B, either in the ABox or in the
     * inferred model, infer that it is of type A.
     */
    protected void addedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) {
        //log.debug("subClass = " + subClass.getURI() + " superClass = " + superClass.getURI());
        OntModel unionModel = VitroModelFactory.createOntologyModel();
        unionModel.addSubModel(aboxModel);
        unionModel.addSubModel(inferenceModel);
        List<Resource> subjectList = new ArrayList<Resource>();
        aboxModel.enterCriticalSection(Lock.READ);
        try {
            StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                subjectList.add(stmt.getSubject());
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }

        for (Resource subject : subjectList) {
            Statement infStmt = ResourceFactory.createStatement(subject, RDF.type, superClass);
            addInference(infStmt, inferenceModel, true);
            setMostSpecificTypes(infStmt.getSubject(), inferenceModel, new HashSet<String>());
        }
    }

    /**
     * If removed that B is a subclass of A, then for each individual
     * that is of type B, either inferred or in the ABox, remove the
     * assertion that it is of type A from the inferred model,
     * UNLESS the individual is of some type C that is a subClass 
     * of A (including A itself)
     */
    protected void removedSubClass(OntClass subClass, OntClass superClass, Model inferenceModel) {
        OntModel unionModel = VitroModelFactory.createOntologyModel();
        unionModel.addSubModel(aboxModel);
        unionModel.addSubModel(inferenceModel);
        List<Resource> subjectList = new ArrayList<Resource>();
        aboxModel.enterCriticalSection(Lock.READ);
        try {
            StmtIterator iter = unionModel.listStatements((Resource) null, RDF.type, subClass);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                subjectList.add(stmt.getSubject());
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }

        for (Resource ind : subjectList) {
            if (entailedType(ind, superClass)) {
                continue;
            }
            Statement infStmt = ResourceFactory.createStatement(ind, RDF.type, superClass);
            inferenceModel.enterCriticalSection(Lock.WRITE);
            try {
                if (inferenceModel.contains(infStmt)) {
                    inferenceModel.remove(infStmt);
                }

            } finally {
                inferenceModel.leaveCriticalSection();
            }

            setMostSpecificTypes(ind, inferenceModel, new HashSet<String>());
        }
    }

    /**
     * If it is added that P is an inverse of Q, then:
     *  1. For each statement involving predicate P in
     *     the assertions model add the inverse statement
     *     to the inference model if that inverse is
     *     in the assertions model.
     *               
     *  2. Repeat the same for predicate Q.
     *  
     */
    protected void addedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) {

        if (!prop1.isObjectProperty() || !prop2.isObjectProperty()) {
            log.warn(
                    "The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: "
                            + prop1.getURI() + " property 2:" + prop2.getURI());
            return;
        }

        Model inferences = ModelFactory.createDefaultModel();
        inferences.add(generateInverseInferences(prop1, prop2));
        inferences.add(generateInverseInferences(prop2, prop1));

        if (inferences.isEmpty())
            return;

        StmtIterator iter = inferences.listStatements();

        while (iter.hasNext()) {
            Statement infStmt = iter.next();
            addInference(infStmt, inferenceModel, true);
        }
    }

    /**
     * If it is removed that P is an inverse of Q, then:
     *  1. For each statement involving predicate P in 
     *     the abox assertions model remove the inverse
     *     statement from the inference model unless
     *     that statement is otherwise entailed.
     *               
     *  2. Repeat the same for predicate Q.
     */
    protected void removedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) {

        if (!prop1.isObjectProperty() || !prop2.isObjectProperty()) {
            log.warn(
                    "The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: "
                            + prop1.getURI() + " property 2:" + prop2.getURI());
            return;
        }

        Model inferences = ModelFactory.createDefaultModel();
        inferences.add(generateInverseInferences(prop1, prop2));
        inferences.add(generateInverseInferences(prop2, prop1));

        if (inferences.isEmpty())
            return;

        StmtIterator iter = inferences.listStatements();

        while (iter.hasNext()) {
            Statement infStmt = iter.next();

            if (entailedStatement(infStmt)) {
                continue;
            }

            removeInference(infStmt, inferenceModel);
        }
    }

    /**
     * Get a list of individuals the same as the given individual
     */
    protected List<Resource> getSameIndividuals(Resource ind, Model inferenceModel) {
        ArrayList<Resource> sameIndividuals = new ArrayList<Resource>();
        fullModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Statement> iter = fullModel.listStatements(ind, OWL.sameAs, (RDFNode) null);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getObject() != null && stmt.getObject().isResource()
                        && stmt.getObject().asResource().getURI() != null) {
                    sameIndividuals.add(stmt.getObject().asResource());
                }
            }
        } finally {
            fullModel.leaveCriticalSection();
        }
        return sameIndividuals;
    }

    /**
     * Materializes inferences based on the owl:sameAs relationship.
     *  
     * If it is added that x owl:sameAs y, then all asserted and inferred
     * statements about x will become inferred about y if they are not already
     * asserted about y, and vice versa.
     */
    protected void addedABoxSameAsAssertion(Statement stmt, Model inferenceModel) {
        Resource subject = null;
        Resource object = null;

        if (stmt.getSubject().isResource()) {
            subject = stmt.getSubject().asResource();
            if (tboxModel.containsResource(subject) || subject.isAnon()) {
                log.debug(
                        "the subject of this sameAs statement is either in the tbox or an anonymous node, no reasoning will be done: "
                                + stmtString(stmt));
                return;
            }
        } else {
            log.warn("the subject of this sameAs statement is not a resource, no reasoning will be done: "
                    + stmtString(stmt));
            return;
        }

        if (stmt.getObject().isResource()) {
            object = stmt.getObject().asResource();
            if (tboxModel.containsResource(object) || object.isAnon()) {
                log.debug(
                        "the object of this sameAs statement is either in the tbox or an anonymous node, no reasoning will be done: "
                                + stmtString(stmt));
                return;
            }
        } else {
            log.warn("the object of this sameAs statement is not a resource, no reasoning will be done: "
                    + stmtString(stmt));
            return;
        }

        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            if (inferenceModel.contains(stmt)) {
                inferenceModel.remove(stmt);
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        Statement opposite = ResourceFactory.createStatement(object, OWL.sameAs, subject);
        addInference(opposite, inferenceModel, true);

        generateSameAsInferences(subject, object, inferenceModel);
        generateSameAsInferences(object, subject, inferenceModel);
    }

    /**
     * Materializes inferences based on the owl:sameAs relationship.
     *  
     * If it is removed   that x is sameAs y, then remove y sameAs x from 
     * the inference graph and then recompute the inferences for x and
     * y based on their respective assertions.
      * that x owl:sameAs y, then all asserted and inferred
     */
    protected void removedABoxSameAsAssertion(Statement stmt, Model inferenceModel) {
        Resource subject = null;
        Resource object = null;

        if (stmt.getSubject().isResource()) {
            subject = stmt.getSubject().asResource();
            if (tboxModel.containsResource(subject) || subject.isAnon()) {
                log.debug(
                        "the subject of this removed sameAs statement is either in the tbox or an anonymous node, no reasoning will be done: "
                                + stmtString(stmt));
                return;
            }
        } else {
            log.warn("the subject of this removed sameAs statement is not a resource, no reasoning will be done: "
                    + stmtString(stmt));
            return;
        }

        if (stmt.getObject().isResource()) {
            object = stmt.getObject().asResource();
            if (tboxModel.containsResource(object) || object.isAnon()) {
                log.debug(
                        "the object of this removed sameAs statement is either in the tbox or an anonymous node, no reasoning will be done: "
                                + stmtString(stmt));
                return;
            }
        } else {
            log.warn("the object of this removed sameAs statement is not a resource, no reasoning will be done: "
                    + stmtString(stmt));
            return;
        }

        List<Resource> sameIndividuals = getSameIndividuals(subject, inferenceModel);
        sameIndividuals.addAll(getSameIndividuals(object, inferenceModel));

        Iterator<Resource> sIter1 = sameIndividuals.iterator();
        while (sIter1.hasNext()) {
            removeInferencesForIndividual(sIter1.next(), inferenceModel);
        }

        Iterator<Resource> sIter2 = sameIndividuals.iterator();
        while (sIter2.hasNext()) {
            computeInferencesForIndividual(sIter2.next(), inferenceModel);
        }
    }

    protected void doSameAsForAddedABoxAssertion(Statement stmt, Model inferenceModel) {
        List<Resource> sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(), inferenceModel);

        Iterator<Resource> sameIter = sameIndividuals.iterator();
        while (sameIter.hasNext()) {
            Resource subject = sameIter.next();
            Statement sameStmt = ResourceFactory.createStatement(subject, stmt.getPredicate(), stmt.getObject());
            addInference(sameStmt, inferenceModel, doSameAs);
        }
    }

    /**
     * Materializes inferences based on the owl:inverseOf relationship.
     * and owl:sameAs
     *  
     * If it is added that x prop1 y, and prop2 is an inverseOf prop1
     * then add y prop2 x to the inference graph, if it is not already in
     * the assertions graph.
     * 
     */
    protected void addedABoxAssertion(Statement stmt, Model inferenceModel) {

        if (!stmt.getObject().isLiteral()) {
            List<OntProperty> inverseProperties = getInverseProperties(stmt);
            Iterator<OntProperty> inverseIter = inverseProperties.iterator();

            while (inverseIter.hasNext()) {
                Property inverseProp = inverseIter.next();
                Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp,
                        stmt.getSubject());
                addInference(infStmt, inferenceModel, true);
            }
        }

        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            if (inferenceModel.contains(stmt)) {
                inferenceModel.remove(stmt);
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        if (doSameAs) {
            doSameAsForAddedABoxAssertion(stmt, inferenceModel);
        }
    }

    void doSameAsForRemovedABoxAssertion(Statement stmt, Model inferenceModel) {
        List<Resource> sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(), inferenceModel);
        Iterator<Resource> sameIter = sameIndividuals.iterator();
        while (sameIter.hasNext()) {
            Statement stmtSame = ResourceFactory.createStatement(sameIter.next(), stmt.getPredicate(),
                    stmt.getObject());
            removeInference(stmtSame, inferenceModel, false, true);
        }
    }

    protected void generateSameAsInferences(Resource ind1, Resource ind2, Model inferenceModel) {

        OntModel unionModel = VitroModelFactory.createOntologyModel();
        unionModel.addSubModel(aboxModel);
        unionModel.addSubModel(inferenceModel);

        aboxModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Statement> iter = unionModel.listStatements(ind1, (Property) null, (RDFNode) null);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getObject() == null)
                    continue;
                Statement infStmt = ResourceFactory.createStatement(ind2, stmt.getPredicate(), stmt.getObject());
                addInference(infStmt, inferenceModel, true);
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }

        return;
    }

    /**
     * Remove inferences for individual
     */
    protected void removeInferencesForIndividual(Resource ind, Model inferenceModel) {

        Model individualInferences = ModelFactory.createDefaultModel();

        inferenceModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Statement> iter = inferenceModel.listStatements(ind, (Property) null, (RDFNode) null);

            while (iter.hasNext()) {
                individualInferences.add(iter.next());
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            inferenceModel.remove(individualInferences);
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        return;
    }

    /**
     * compute inferences for individual
     */
    protected void computeInferencesForIndividual(Resource ind, Model inferenceModel) {

        Iterator<Statement> iter = null;
        aboxModel.enterCriticalSection(Lock.WRITE);
        try {
            iter = aboxModel.listStatements(ind, (Property) null, (RDFNode) null);
        } finally {
            aboxModel.leaveCriticalSection();
        }

        while (iter.hasNext()) {
            Statement stmt = iter.next();
            addedStatement(stmt);
        }

        return;
    }

    /**
      * Returns true if it is entailed by class subsumption that
      * subject is of type cls; otherwise returns false.
      */
    protected boolean entailedType(Resource subject, Resource cls) {
        return entailedType(subject, cls, null);
    }

    /**
      * Returns true if it is entailed by class subsumption that
     * subject is of type cls; otherwise returns false.
     * remainingTypeURIs is an optional list of asserted type URIs for the subject 
     * resource, and may be null.  Supplying a precompiled list can yield performance 
     * improvement when this method is called repeatedly for the same subject. 
      */
    protected boolean entailedType(Resource subject, Resource cls, List<String> remainingTypeURIs) {

        List<Resource> subClasses = getSubClasses(cls);
        Set<String> subClassURIs = new HashSet<String>();
        for (Resource subClass : subClasses) {
            if (!subClass.isAnon()) {
                subClassURIs.add(subClass.getURI());
            }
        }

        List<String> typeURIs = (remainingTypeURIs == null) ? getRemainingAssertedTypeURIs(subject)
                : remainingTypeURIs;

        for (String typeURI : typeURIs) {
            if (!typeURI.equals(cls.getURI()) && subClassURIs.contains(typeURI)) {
                return true;
            }
        }

        return false;

    }

    protected List<String> getRemainingAssertedTypeURIs(Resource resource) {

        List<String> typeURIs = new ArrayList<String>();

        List<Resource> sameIndividuals = getSameIndividuals(resource, inferenceModel);
        sameIndividuals.add(resource);

        aboxModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Resource> sameIter = sameIndividuals.iterator();
            while (sameIter.hasNext()) {
                Resource res = sameIter.next();
                StmtIterator typeIt = aboxModel.listStatements(res, RDF.type, (RDFNode) null);
                while (typeIt.hasNext()) {
                    Statement stmt = typeIt.nextStatement();
                    if (stmt.getObject().isURIResource()) {
                        String typeURI = stmt.getObject().asResource().getURI();
                        typeURIs.add(typeURI);
                    }
                }
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }

        return typeURIs;
    }

    protected List<Resource> getSubClasses(Resource cls) {
        List<Resource> subClasses = new ArrayList<Resource>();
        tboxModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Statement> iter = tboxModel.listStatements((Resource) null, RDFS.subClassOf, cls);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getSubject() == null || stmt.getSubject().asResource().getURI() == null)
                    continue;
                if (!subClasses.contains(stmt.getSubject())) {
                    subClasses.add(stmt.getSubject());
                }
            }

            iter = tboxModel.listStatements((Resource) null, OWL.equivalentClass, cls);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getSubject() == null || stmt.getSubject().getURI() == null)
                    continue;
                if (!subClasses.contains(stmt.getSubject())) {
                    subClasses.add(stmt.getSubject());
                }
            }

            return subClasses;
        } finally {
            tboxModel.leaveCriticalSection();
        }
    }

    protected List<Resource> getSuperClasses(Resource cls) {
        List<Resource> superClasses = new ArrayList<Resource>();
        tboxModel.enterCriticalSection(Lock.READ);
        try {
            Iterator<Statement> iter = tboxModel.listStatements(cls, RDFS.subClassOf, (RDFNode) null);
            while (iter.hasNext()) {
                Statement stmt = iter.next();

                if (stmt.getObject() != null && stmt.getObject().isResource()) {
                    Resource superCls = stmt.getObject().asResource();

                    if (superCls.isAnon() || superClasses.contains(superCls)) {
                        continue;
                    }
                    superClasses.add(superCls);
                }
            }

            iter = tboxModel.listStatements((Resource) null, OWL.equivalentClass, cls);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getSubject() == null || stmt.getSubject().asResource().getURI() == null)
                    continue;
                if (!superClasses.contains(stmt.getSubject())) {
                    superClasses.add(stmt.getSubject());
                }
            }
            return superClasses;
        } finally {
            tboxModel.leaveCriticalSection();
        }
    }

    /**
      * Returns true if the triple is entailed by inverse property
     * reasoning or sameAs reasoning; otherwise returns false.
      */
    protected boolean entailedStatement(Statement stmt) {
        //TODO think about checking class subsumption here (for convenience)

        // Inverse properties
        List<OntProperty> inverses = getInverseProperties(stmt);
        Iterator<OntProperty> iIter = inverses.iterator();
        if (iIter.hasNext()) {
            aboxModel.enterCriticalSection(Lock.READ);
            try {
                while (iIter.hasNext()) {
                    Property invProp = iIter.next();
                    Statement invStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), invProp,
                            stmt.getSubject());
                    if (aboxModel.contains(invStmt)) {
                        return true;
                    }
                }
            } finally {
                aboxModel.leaveCriticalSection();
            }
        }

        // individuals sameAs each other
        if (doSameAs) {
            List<Resource> sameIndividuals = getSameIndividuals(stmt.getSubject().asResource(), inferenceModel);
            Iterator<Resource> rIter = sameIndividuals.iterator();
            if (rIter.hasNext()) {
                aboxModel.enterCriticalSection(Lock.READ);
                try {
                    while (rIter.hasNext()) {
                        Resource subject = rIter.next();

                        if (aboxModel.contains(subject, stmt.getPredicate(), stmt.getObject())) {
                            return true;
                        }
                    }
                } finally {
                    aboxModel.leaveCriticalSection();
                }
            }
        }

        return false;
    }

    /**
     * Returns a list of properties that are inverses of the property
     * in the given statement. 
     */
    protected List<OntProperty> getInverseProperties(Statement stmt) {

        List<OntProperty> inverses = new ArrayList<OntProperty>();

        if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
            return inverses;
        }

        tboxModel.enterCriticalSection(Lock.READ);
        try {

            OntProperty prop = tboxModel.getOntProperty(stmt.getPredicate().getURI());

            if (prop != null) {
                if (!prop.isObjectProperty()) {
                    return inverses;
                }

                if (!stmt.getObject().isResource()) {
                    log.debug("The predicate of this statement is an object property, "
                            + "but the object is not a resource.");
                    return inverses;
                }

                // not reasoning on properties in the OWL, RDF or RDFS namespace
                if ((prop.getNameSpace()).equals(OWL.NS) || (prop.getNameSpace()).equals(RDFS.getURI())
                        || (prop.getNameSpace()).equals(RDF.getURI())) {
                    return inverses;
                }

                ExtendedIterator<? extends OntProperty> iter = prop.listInverse();

                while (iter.hasNext()) {
                    OntProperty invProp = iter.next();

                    if ((invProp.getNameSpace()).equals(OWL.NS) || (invProp.getNameSpace()).equals(RDFS.getURI())
                            || (invProp.getNameSpace()).equals(RDF.getURI())) {
                        continue;
                    }
                    inverses.add(invProp);
                }
            }
        } finally {
            tboxModel.leaveCriticalSection();
        }

        return inverses;
    }

    protected Model generateInverseInferences(OntProperty prop, OntProperty inverseProp) {
        Model inferences = ModelFactory.createDefaultModel();

        aboxModel.enterCriticalSection(Lock.READ);
        try {
            StmtIterator iter = aboxModel.listStatements((Resource) null, prop, (RDFNode) null);

            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (!stmt.getObject().isResource())
                    continue;
                Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp,
                        stmt.getSubject());
                inferences.add(infStmt);
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }

        return inferences;
    }

    /**
     * Add an inference from the inference model
     * 
     * Adds the inference to the inference model if it is not already in
     * the inference model and not in the abox model.
     */

    public void addInference(Statement infStmt, Model inferenceModel) {
        addInference(infStmt, inferenceModel, true);
    }

    protected void addInference(Statement infStmt, Model inferenceModel, boolean handleSameAs) {
        addInference(infStmt, inferenceModel, handleSameAs, true);
    }

    protected void addInference(Statement infStmt, Model inferenceModel, boolean handleSameAs,
            boolean checkRedundancy) {

        aboxModel.enterCriticalSection(Lock.READ);
        try {
            inferenceModel.enterCriticalSection(Lock.WRITE);
            try {
                if (!checkRedundancy || (!inferenceModel.contains(infStmt) && !aboxModel.contains(infStmt))) {
                    inferenceModel.add(infStmt);
                }

                if (handleSameAs) {
                    List<Resource> sameIndividuals = getSameIndividuals(infStmt.getSubject().asResource(),
                            inferenceModel);
                    Iterator<Resource> sameIter = sameIndividuals.iterator();
                    while (sameIter.hasNext()) {
                        Resource subject = sameIter.next();

                        Statement sameStmt = ResourceFactory.createStatement(subject, infStmt.getPredicate(),
                                infStmt.getObject());
                        if (subject.equals(infStmt.getObject()) && OWL.sameAs.equals(infStmt.getPredicate())) {
                            continue;
                        }

                        if (!inferenceModel.contains(sameStmt) && !aboxModel.contains(sameStmt)) {
                            inferenceModel.add(sameStmt);
                        }
                    }
                }
            } finally {
                inferenceModel.leaveCriticalSection();
            }
        } finally {
            aboxModel.leaveCriticalSection();
        }
    }

    /**
     * Remove an inference from the inference model
     * 
     * Removes the inference if it is not entailed by the abox model
     * and if the inference model contains it.
     * 
     * Removes the corresponding inference for each same individual
     * if that inference is not entailed by the abox model. 
     */
    public void removeInference(Statement infStmt, Model inferenceModel) {
        removeInference(infStmt, inferenceModel, true, true);
    }

    protected void removeInference(Statement infStmt, Model inferenceModel, boolean handleSameAs,
            boolean checkEntailment) {

        inferenceModel.enterCriticalSection(Lock.WRITE);
        try {
            if ((!checkEntailment || !entailedStatement(infStmt)) && inferenceModel.contains(infStmt)) {
                inferenceModel.remove(infStmt);
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        if (handleSameAs) {
            inferenceModel.enterCriticalSection(Lock.WRITE);
            try {
                List<Resource> sameIndividuals = getSameIndividuals(infStmt.getSubject().asResource(),
                        inferenceModel);

                Iterator<Resource> sameIter = sameIndividuals.iterator();
                while (sameIter.hasNext()) {
                    Statement infStmtSame = ResourceFactory.createStatement(sameIter.next(), infStmt.getPredicate(),
                            infStmt.getObject());
                    if ((!checkEntailment || !entailedStatement(infStmtSame))
                            && inferenceModel.contains(infStmtSame)) {
                        inferenceModel.remove(infStmtSame);
                    }
                }
            } finally {
                inferenceModel.leaveCriticalSection();
            }
        }
    }

    /**
      * Find the most specific types (classes) of an individual and
      * infer them for the individual with the mostSpecificType
      * annotation.
     */
    protected void setMostSpecificTypes(Resource individual, Model inferenceModel, HashSet<String> unknownTypes) {

        tboxModel.enterCriticalSection(Lock.READ);
        aboxModel.enterCriticalSection(Lock.READ);
        inferenceModel.enterCriticalSection(Lock.READ);
        HashSet<String> typeURIs = new HashSet<String>();

        try {
            OntModel unionModel = VitroModelFactory.createOntologyModel();
            unionModel.addSubModel(aboxModel);
            unionModel.addSubModel(inferenceModel);

            List<OntClass> types = new ArrayList<OntClass>();

            StmtIterator stmtIter = unionModel.listStatements(individual, RDF.type, (RDFNode) null);

            while (stmtIter.hasNext()) {

                Statement stmt = stmtIter.next();

                if (!stmt.getObject().isResource()) {
                    log.warn("The object of this rdf:type assertion is expected to be a resource: "
                            + stmtString(stmt));
                    continue;
                }

                OntClass ontClass = null;

                if ((stmt.getObject().asResource()).getURI() != null) {
                    ontClass = tboxModel.getOntClass(stmt.getObject().asResource().getURI());
                } else {
                    log.debug("The object of this rdf:type assertion has a null URI: " + stmtString(stmt));
                    continue;
                }

                if (ontClass == null) {
                    if (!(stmt.getObject().asResource().getNameSpace()).equals(OWL.NS)) {
                        if (!unknownTypes.contains(stmt.getObject().asResource().getURI())) {
                            unknownTypes.add(stmt.getObject().asResource().getURI());
                            log.debug(
                                    "Didn't find the target class (the object of an asserted or inferred rdf:type statement) in the TBox: "
                                            + (stmt.getObject().asResource()).getURI()
                                            + ". No mostSpecificType computation will be done based on "
                                            + (stmt.getObject().asResource()).getURI() + " type statements.");
                        }
                    }
                    continue;
                }

                if (ontClass.isAnon())
                    continue;

                types.add(ontClass);
            }

            List<OntClass> types2 = new ArrayList<OntClass>();
            types2.addAll(types);

            Iterator<OntClass> typeIter = types.iterator();

            while (typeIter.hasNext()) {
                OntClass type = typeIter.next();

                boolean add = true;
                Iterator<OntClass> typeIter2 = types2.iterator();
                while (typeIter2.hasNext()) {
                    OntClass type2 = typeIter2.next();

                    if (type.equals(type2)) {
                        continue;
                    }

                    if (type.hasSubClass(type2, false) && !type2.hasSubClass(type, false)) {
                        add = false;
                        break;
                    }
                }

                if (add) {
                    typeURIs.add(type.getURI());

                    ArrayList<Resource> equivalentClasses = new ArrayList<Resource>();

                    Iterator<Statement> iter = tboxModel.listStatements((Resource) null, OWL.equivalentClass, type);
                    while (iter.hasNext()) {
                        Statement stmt = iter.next();
                        Resource res = stmt.getSubject();
                        if ((res == null) || res.isAnon() || equivalentClasses.contains(res)) {
                            continue;
                        }
                        equivalentClasses.add(res);
                    }

                    Iterator<Resource> eIter = equivalentClasses.iterator();

                    while (eIter.hasNext()) {
                        Resource equivClass = eIter.next();
                        if (equivClass.isAnon())
                            continue;
                        typeURIs.add(equivClass.getURI());
                    }
                }
            }
        } finally {
            inferenceModel.leaveCriticalSection();
            aboxModel.leaveCriticalSection();
            tboxModel.leaveCriticalSection();
        }

        setMostSpecificTypes(individual, typeURIs, inferenceModel);
        return;
    }

    protected void setMostSpecificTypes(Resource individual, HashSet<String> typeURIs, Model inferenceModel) {

        Model retractions = ModelFactory.createDefaultModel();

        inferenceModel.enterCriticalSection(Lock.READ);
        try {
            // remove obsolete mostSpecificType assertions
            StmtIterator iter = inferenceModel.listStatements(individual, mostSpecificType, (RDFNode) null);

            while (iter.hasNext()) {
                Statement stmt = iter.next();

                if (!stmt.getObject().isResource()) {
                    log.warn("The object of this assertion is expected to be a resource: " + stmtString(stmt));
                    continue;
                }

                if (!typeURIs.contains(stmt.getObject().asResource().getURI())) {
                    retractions.add(stmt);
                }
            }
        } finally {
            inferenceModel.leaveCriticalSection();
        }

        Iterator<Statement> rIter = retractions.listStatements();
        while (rIter.hasNext()) {
            removeInference(rIter.next(), inferenceModel, true, false);
        }

        Iterator<String> typeIter = typeURIs.iterator();
        while (typeIter.hasNext()) {
            String typeURI = typeIter.next();
            Statement mstStmt = ResourceFactory.createStatement(individual, mostSpecificType,
                    ResourceFactory.createResource(typeURI));
            addInference(mstStmt, inferenceModel, true);
        }

        return;
    }

    protected List<Resource> getParents(Resource cls, OntModel tboxModel) {

        List<Resource> parents = new ArrayList<Resource>();

        tboxModel.enterCriticalSection(Lock.READ);
        try {
            StmtIterator iter = tboxModel.listStatements(cls, RDFS.subClassOf, (RDFNode) null);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getObject() == null || !stmt.getObject().isResource()
                        || stmt.getObject().asResource().isAnon()) {
                    continue;
                }
                if (!parents.contains(stmt.getObject().asResource())) {
                    parents.add(stmt.getObject().asResource());
                }
            }

            iter = tboxModel.listStatements(cls, OWL.equivalentClass, (RDFNode) null);
            while (iter.hasNext()) {
                Statement stmt = iter.next();
                if (stmt.getObject() == null || !stmt.getObject().isResource()
                        || stmt.getObject().asResource().isAnon()) {
                    continue;
                }
                if (!parents.contains(stmt.getObject().asResource())) {
                    parents.add(stmt.getObject().asResource());
                }
            }
        } catch (Exception e) {
            log.error("problem computing type inferences for: " + cls.getURI() + e.getMessage());
        } finally {
            tboxModel.leaveCriticalSection();
        }

        return parents;
    }

    // system-configured reasoning modules (plugins)
    protected boolean isInterestedInRemovedStatement(Statement stmt) {

        if (stmt.getPredicate().equals(RDF.type))
            return true;

        for (ReasonerPlugin plugin : getPluginList()) {
            if (plugin.isInterestedInRemovedStatement(stmt))
                return true;
        }

        return false;
    }

    protected void doPlugins(ModelUpdate.Operation op, Statement stmt) {

        for (ReasonerPlugin plugin : getPluginList()) {
            try {
                switch (op) {
                case ADD:
                    if (plugin.isInterestedInAddedStatement(stmt)) {
                        plugin.addedABoxStatement(stmt, aboxModel, inferenceModel, tboxModel);
                    }
                    break;
                case RETRACT:
                    if (plugin.isInterestedInRemovedStatement(stmt)) {
                        plugin.removedABoxStatement(stmt, aboxModel, inferenceModel, tboxModel);
                    }
                    break;
                }
            } catch (Exception e) {
                log.error(
                        "Exception while processing " + (op == ModelUpdate.Operation.ADD ? "an added" : "a removed")
                                + " statement in SimpleReasoner plugin:" + plugin.getClass().getName() + " -- ",
                        e);
            }
        }
    }

    /**
     * Returns true if the reasoner is in the process of recomputing all
     * inferences.
     */

    public boolean isRecomputing() {
        if (recomputer == null) {
            return false;
        }

        return recomputer.isRecomputing();
    }

    public void recompute() {
        if (recomputer != null) {
            recomputer.recompute();
        }
    }

    /**
     * This is called when the application shuts down.
     */
    public void setStopRequested() {
        if (recomputer != null) {
            recomputer.setStopRequested();
        }

        this.stopRequested = true;
    }

    // DeltaComputer    
    /**
     * Asynchronous reasoning mode (DeltaComputer) is used in the case of batch removals. 
     */
    public boolean isABoxReasoningAsynchronous() {
        if (batchMode > 0) {
            return true;
        } else {
            return false;
        }
    }

    private volatile boolean deltaComputerProcessing = false;
    private int eventCount = 0;

    @Override
    public void notifyEvent(Model model, Object event) {

        if (event instanceof BulkUpdateEvent) {
            handleBulkUpdateEvent(event);
        }
    }

    public synchronized void handleBulkUpdateEvent(Object event) {

        if (event instanceof BulkUpdateEvent) {
            if (((BulkUpdateEvent) event).getBegin()) {

                log.info("received a bulk update begin event");
                if (deltaComputerProcessing) {
                    eventCount++;
                    log.info(
                            "received a bulk update begin event while processing in asynchronous mode. Event count = "
                                    + eventCount);
                    return;
                } else {
                    batchMode = 1;
                    if (aBoxDeltaModeler1.getRetractions().size() > 0) {
                        log.warn(
                                "Unexpected condition: the aBoxDeltaModeler1 retractions model was not empty when entering batch mode.");
                    }

                    if (aBoxDeltaModeler2.getRetractions().size() > 0) {
                        log.warn(
                                "Unexpected condition: the aBoxDeltaModeler2 retractions model was not empty when entering batch mode.");
                    }

                    log.info("initializing batch mode 1");
                }
            } else {
                log.info("received a bulk update end event");
                if (!deltaComputerProcessing) {
                    deltaComputerProcessing = true;
                    VitroBackgroundThread thread = new VitroBackgroundThread(new DeltaComputer(),
                            "SimpleReasoner.DeltaComputer");
                    thread.setWorkLevel(WORKING);
                    thread.start();
                } else {
                    eventCount--;
                    log.info(
                            "received a bulk update end event while currently processing in aynchronous mode. Event count = "
                                    + eventCount);
                }
            }
        }
    }

    private synchronized boolean switchBatchModes() {

        if (batchMode == 1) {
            aBoxDeltaModeler2.getRetractions().removeAll();

            if (aBoxDeltaModeler1.getRetractions().size() > 0) {
                batchMode = 2;
                log.info("entering batch mode " + batchMode);
            } else {
                deltaComputerProcessing = false;
                if (eventCount == 0) {
                    batchMode = 0;
                }
            }
        } else if (batchMode == 2) {
            aBoxDeltaModeler1.getRetractions().removeAll();

            if (aBoxDeltaModeler2.getRetractions().size() > 0) {
                batchMode = 1;
                log.info("entering batch mode " + batchMode);
            } else {
                deltaComputerProcessing = false;
                if (eventCount == 0) {
                    batchMode = 0;
                }
            }
        } else {
            log.warn("unexpected condition, invoked when batchMode is neither 1 nor 2. batchMode = " + batchMode);
            deltaComputerProcessing = false;
        }

        return deltaComputerProcessing;
    }

    private class DeltaComputer extends Thread {
        public DeltaComputer() {
        }

        @Override
        public void run() {
            log.info("starting DeltaComputer.run");
            boolean abort = false;
            Model retractions = ModelFactory.createDefaultModel();
            String qualifier = "";

            while (deltaComputerProcessing && !stopRequested) {

                if (switchBatchModes()) {
                    if (batchMode == 1) {
                        qualifier = "2";
                        retractions = aBoxDeltaModeler2.getRetractions();
                    } else if (batchMode == 2) {
                        qualifier = "1";
                        retractions = aBoxDeltaModeler1.getRetractions();
                    }
                } else {
                    break;
                }

                retractions.enterCriticalSection(Lock.READ);
                int num = 0;

                try {
                    log.info("started computing inferences for batch " + qualifier + " updates");

                    ResIterator subIt = retractions.listSubjects();
                    while (subIt.hasNext()) {
                        Resource subj = subIt.nextResource();
                        StmtIterator iter = retractions.listStatements(subj, null, (RDFNode) null);
                        boolean typesModified = false;
                        try {
                            List<String> typeURIs = null;
                            while (iter.hasNext() && !stopRequested) {
                                Statement stmt = iter.next();
                                num++;
                                try {
                                    if (stmt.getPredicate().equals(RDF.type)) {
                                        typesModified = true;
                                        if (typeURIs == null) {
                                            typeURIs = getRemainingAssertedTypeURIs(stmt.getSubject());
                                        }
                                        removedABoxTypeAssertion(stmt, inferenceModel, typeURIs);
                                    } else if (doSameAs && stmt.getPredicate().equals(OWL.sameAs)) {
                                        removedABoxSameAsAssertion(stmt, inferenceModel);
                                    } else {
                                        removedABoxAssertion(stmt, inferenceModel);
                                    }
                                    doPlugins(ModelUpdate.Operation.RETRACT, stmt);
                                } catch (NullPointerException npe) {
                                    abort = true;
                                    break;
                                } catch (Exception e) {
                                    log.error("exception in batch mode ", e);
                                }

                                if ((num % 6000) == 0) {
                                    log.info("still computing inferences for batch " + qualifier + " update...");
                                }

                                if (stopRequested) {
                                    log.info(
                                            "a stopRequested signal was received during DeltaComputer.run. Halting Processing.");
                                    return;
                                }
                            }
                        } finally {
                            iter.close();
                            if (typesModified) {
                                setMostSpecificTypes(subj, inferenceModel, new HashSet<String>());
                            }
                        }
                    }
                } finally {
                    retractions.removeAll();
                    retractions.leaveCriticalSection();
                }

                if (stopRequested) {
                    log.info("a stopRequested signal was received during DeltaComputer.run. Halting Processing.");
                    deltaComputerProcessing = false;
                    return;
                }

                if (abort) {
                    log.error("a NullPointerException was received while computing inferences in batch " + qualifier
                            + " mode. Halting inference computation.");
                    deltaComputerProcessing = false;
                    return;
                }

                log.info("finished computing inferences for batch " + qualifier + " updates");
                log.debug("\t--> processed " + num + " statements");
            }

            log.info("ending DeltaComputer.run. batchMode = " + batchMode);
        }
    }

    /**
     * Utility method for logging
     */
    public static String stmtString(Statement statement) {
        return " [subject = " + statement.getSubject().getURI() + "] [property = "
                + statement.getPredicate().getURI() + "] [object = "
                + (statement.getObject().isLiteral()
                        ? ((Literal) statement.getObject()).getLexicalForm() + " (Literal)"
                        : ((Resource) statement.getObject()).getURI() + " (Resource)")
                + "]";
    }

}