diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java index 1ce24d4df..fc76d3efa 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/ontology/update/ABoxUpdater.java @@ -1,3 +1,5 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + package edu.cornell.mannlib.vitro.webapp.ontology.update; import java.io.IOException; @@ -6,16 +8,28 @@ import java.util.List; import com.hp.hpl.jena.ontology.OntClass; import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.ontology.OntModelSpec; import com.hp.hpl.jena.ontology.OntProperty; 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.Resource; import com.hp.hpl.jena.rdf.model.ResourceFactory; -import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; +import com.hp.hpl.jena.vocabulary.OWL; +import com.hp.hpl.jena.vocabulary.RDF; +import edu.cornell.mannlib.vitro.webapp.ontology.update.AtomicOntologyChange.AtomicChangeType; + + +/** +* Performs knowledge base updates to the abox to align with a new ontology version +* +*/ public class ABoxUpdater { private OntModel oldTboxModel; @@ -23,12 +37,26 @@ public class ABoxUpdater { private OntModel aboxModel; private OntologyChangeLogger logger; private OntologyChangeRecord record; - + + /** + * + * Constructor + * + * @param oldTboxModel - previous version of the ontology + * @param newTboxModel - new version of the ontology + * @param aboxModel - the knowledge base to be updated + * @param logger - for writing to the change log + * and the error log. + * @param record - for writing to the additions model + * and the retractions model. + * + */ public ABoxUpdater(OntModel oldTboxModel, OntModel newTboxModel, OntModel aboxModel, OntologyChangeLogger logger, OntologyChangeRecord record) { + this.oldTboxModel = oldTboxModel; this.newTboxModel = newTboxModel; this.aboxModel = aboxModel; @@ -36,22 +64,182 @@ public class ABoxUpdater { this.record = record; } - public void processClassChanges(List changes) throws Exception { + /** + * + * Update a knowledge base to align with changes in the class definitions in + * a new version of the ontology. The two versions of the ontology and the + * knowledge base to be updated are provided in the class constructor and + * are referenced via class level variables. + * + * @param changes - a list of AtomicOntologyChange objects, each representing + * one change in class definition in the new version of the + * ontology. + * + * Writes to the change log file, the error log file, and the incremental change + * knowledge base. + */ + public void processClassChanges(List changes) throws IOException { + Iterator iter = changes.iterator(); + + while (iter.hasNext()) { + AtomicOntologyChange change = iter.next(); + + switch (change.getAtomicChangeType()){ + case ADD: + addClass(change); + break; + case DELETE: + deleteClass(change); + break; + case RENAME: + renameClass(change); + break; + default: + //TODO - get method name automatically + logger.logError(this.getClass().getName() + " processClassChanges: unexpected change type indicator: " + change.getAtomicChangeType()); + } + } } + /** + * + * Update a knowledge based on a class rename in the ontology. All references to the + * old class URI in either the subject or the object position of a statement are + * changed to use the new class URI. + * + * @param change - an AtomicOntologyChange object representing a class + * rename operation. + * + */ public void renameClass(AtomicOntologyChange change) throws IOException { - + OntModel additions = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + OntModel retractions = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + + Resource oldClass = ResourceFactory.createResource(change.getSourceURI()); + Resource newClass = ResourceFactory.createResource(change.getDestinationURI()); + + // Change class references in the subjects of statements + StmtIterator iter = aboxModel.listStatements(oldClass, (Property) null, (RDFNode) null); + + // TODO - catch and report exceptions in adding and removing from the model + while (iter.hasNext()) { + Statement oldStatement = iter.next(); + Statement newStatement = ResourceFactory.createStatement(newClass, oldStatement.getPredicate(), oldStatement.getObject()); + retractions.add(oldStatement); + additions.add(newStatement); + logChange(oldStatement, false); + logChange(newStatement,true); + } + + // Change class references in the objects of statements + iter = aboxModel.listStatements((Resource) null, (Property) null, oldClass); + + // TODO - catch and report exceptions in adding and removing from the model + while (iter.hasNext()) { + Statement oldStatement = iter.next(); + Statement newStatement = ResourceFactory.createStatement(oldStatement.getSubject(), oldStatement.getPredicate(), newClass); + retractions.add(oldStatement); + additions.add(newStatement); + //TODO - worried about logging changes before the changes are already made + // in the model + logChanges(oldStatement, newStatement); + } + + aboxModel.remove(retractions); + record.recordRetractions(retractions); + aboxModel.add(additions); + record.recordAdditions(additions); } - public void addClass(AtomicOntologyChange change) { - - + /** + * + * Examine a knowledge based on a class addition to the ontology, and + * add messages to the change log indicating where manual review is + * recommended. If the added class has a parent in the new ontology + * that is not OWL.Thing, and if the knowledge base contains individuals + * asserted to be in the parent class, then log message recommending + * review of those individuals to see whether they are of the new + * class type. + * + * @param change - an AtomicOntologyChange object representing a class + * addition operation. + * + */ + public void addClass(AtomicOntologyChange change) throws IOException { + + //TODO - is sourceURI the right thing here? + OntClass addedClass = newTboxModel.getOntClass(change.getSourceURI()); + + if (addedClass == null) { + // TODO - log + return; + } + + ExtendedIterator classIter = addedClass.listSuperClasses(); + + while (classIter.hasNext()) { + OntClass parentOfAddedClass = classIter.next(); + + if (!parentOfAddedClass.equals(OWL.Thing)) { + + StmtIterator stmtIter = aboxModel.listStatements(null, RDF.type, parentOfAddedClass); + + int count = stmtIter.toList().size(); + if (count > 0) { + + String indList = ""; + while (stmtIter.hasNext()) { + Statement stmt = stmtIter.next(); + indList += "\n\t" + stmt.getSubject().getURI(); + } + + logger.log("There are " + count + " individuals in the model that are of type " + parentOfAddedClass.getURI() + "," + + " and a new subclass of that class has been added: " + addedClass.getURI() + ". " + + "Please review the following individuals to see whether they should be of type: " + addedClass.getURI() + ":" + + indList ); + } + } + } } - public void deleteClass(AtomicOntologyChange change) { + /** + * + * Update a knowledge base to account for a class deletion from the ontology. + * All references to the deleted class URI in either the subject or the object + * position of a statement are changed to use the closest available parent of + * the deleted class from the previous ontology that remains in the new version + * of the ontology. If the deleted class has more than one closest available parent, + * then no change is made to the knowledge base, and message indicating that manual + * review is necessary is added to the change log. + * + * @param change - an AtomicOntologyChange object representing a class + * delete operation. + * + */ + public void deleteClass(AtomicOntologyChange change) throws IOException { + + OntClass deletedClass = oldTboxModel.getOntClass(change.getSourceURI()); + if (deletedClass == null) { + logger.logError(this.getClass().getName() + " : deleteClass - didn't find the deleted class " + change.getSourceURI() + " in the old model."); + return; + } + //TODO - what if there are multiple parents? maybe + // in that case don't do the rename, but rather + // log a message that they will have to review? + OntClass parent = deletedClass.getSuperClass(); + OntClass replacementClass = newTboxModel.getOntClass(parent.getURI()); + + while (replacementClass == null) { + //ditto + parent = parent.getSuperClass(); + replacementClass = newTboxModel.getOntClass(parent.getURI()); + } + + AtomicOntologyChange chg = new AtomicOntologyChange(deletedClass.getURI(), replacementClass.getURI(), AtomicChangeType.RENAME); + renameClass(chg); } public void processPropertyChanges(List changes) throws IOException { @@ -131,12 +319,17 @@ public class ABoxUpdater { record.recordAdditions(renamePropAddModel); record.recordRetractions(renamePropRetractModel); } + + public void logChanges(Statement oldStatement, Statement newStatement) throws IOException { + logChange(oldStatement,false); + logChange(newStatement,true); + } + public void logChange(Statement statement, boolean add) throws IOException { logger.log( (add ? "Added " : "Removed") + "Statement: subject = " + statement.getSubject().getURI() + " property = " + statement.getPredicate().getURI() + - " object = " + (statement.getObject().isLiteral() ? ((Resource)statement.getObject()).getURI() : ((Literal)statement.getObject()).getLexicalForm())); + " object = " + (statement.getObject().isLiteral() ? ((Resource)statement.getObject()).getURI() + : ((Literal)statement.getObject()).getLexicalForm())); } - - }