From 4b71c1d6bb163ca704d81b0dd69523ef4483bfd0 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Mon, 8 Dec 2014 10:54:07 -0500 Subject: [PATCH] VIVO-778 Refactor the connections to the TBox reasoner. Replace PelletListener with a TBoxReasonerModule in the Application. The reasoner will be accessible only through here, not as a context attribute or through the WebappDaoFactory. Split out the initialization of the TBox reasoner from SimpleReasonerSetup. Break out the filtering of TBox changes into a ConfiguredReasonerListener. Refactor the threading logic into a BasicTBoxReasonerDriver. Add a factory for creating VitroBackgroundThreads in an Executor. Isolate the actual reasoner into a TBoxReasoner implementation. In this case, PelletTBoxReasoner. Combine the consistency flag, error flag, explanation, and running status into one TBoxReasonerStatus object. --- webapp/config/log4j.properties | 1 - .../webapp/application/ApplicationImpl.java | 48 ++ .../freemarker/BaseSiteAdminController.java | 34 +- .../controller/jena/JenaAdminActions.java | 7 +- .../webapp/dao/jena/DataPropertyDaoJena.java | 9 +- .../vitro/webapp/dao/jena/JenaBaseDao.java | 5 +- .../webapp/dao/jena/WebappDaoFactoryJena.java | 15 - .../ObjectPropertyStatementPattern.java | 52 -- ...ObjectPropertyStatementPatternFactory.java | 26 - .../dao/jena/pellet/PelletListener.java | 732 ------------------ .../fields/IndividualsViaVClassOptions.java | 19 +- .../vitro/webapp/modules/Application.java | 3 + .../tboxreasoner/TBoxReasonerModule.java | 29 + .../tboxreasoner/TBoxReasonerStatus.java | 29 + .../webapp/servlet/setup/FileGraphSetup.java | 7 - .../servlet/setup/SimpleReasonerSetup.java | 67 +- .../servlet/setup/UpdateKnowledgeBase.java | 6 +- .../ConfiguredReasonerListener.java | 349 +++++++++ .../tboxreasoner/InferenceModelUpdater.java | 119 +++ .../tboxreasoner/PatternListBuilder.java | 81 ++ .../ReasonerConfiguration.java | 84 +- .../ReasonerStatementPattern.java | 94 +++ .../webapp/tboxreasoner/TBoxChanges.java | 83 ++ .../webapp/tboxreasoner/TBoxReasoner.java | 87 +++ .../tboxreasoner/TBoxReasonerDriver.java | 17 + .../impl/BasicTBoxReasonerDriver.java | 253 ++++++ .../impl/pellet/PelletTBoxReasoner.java | 114 +++ .../impl/pellet/PelletTBoxReasonerModule.java | 102 +++ .../utils/threads/VitroBackgroundThread.java | 23 + .../vitro/webapp/modules/ApplicationStub.java | 8 + .../WEB-INF/resources/startup_listeners.txt | 1 + .../siteAdmin/siteAdmin-ontologyEditor.ftl | 8 +- 32 files changed, 1526 insertions(+), 986 deletions(-) delete mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPattern.java delete mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPatternFactory.java delete mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/PelletListener.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerModule.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerStatus.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ConfiguredReasonerListener.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/InferenceModelUpdater.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/PatternListBuilder.java rename webapp/src/edu/cornell/mannlib/vitro/webapp/{dao/jena/pellet => tboxreasoner}/ReasonerConfiguration.java (52%) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerStatementPattern.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxChanges.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasoner.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasonerDriver.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/BasicTBoxReasonerDriver.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasoner.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasonerModule.java diff --git a/webapp/config/log4j.properties b/webapp/config/log4j.properties index 13c54f2c9..241df5716 100644 --- a/webapp/config/log4j.properties +++ b/webapp/config/log4j.properties @@ -35,7 +35,6 @@ log4j.rootLogger=INFO, AllAppender # These classes are too chatty to display INFO messages. log4j.logger.edu.cornell.mannlib.vitro.webapp.startup.StartupStatus=WARN -log4j.logger.edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.PelletListener=WARN log4j.logger.edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateKnowledgeBase=DEBUG # Spring as a whole is too chatty to display INFO messages. diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java index 544340e00..45842830f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java @@ -16,6 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus; import edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage; import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ConfigurationTripleSource; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; @@ -41,6 +42,7 @@ public class ApplicationImpl implements Application { private FileStorage fileStorage; private ContentTripleSource contentTripleSource; private ConfigurationTripleSource configurationTripleSource; + private TBoxReasonerModule tboxReasonerModule; public void setServletContext(ServletContext ctx) { this.ctx = ctx; @@ -140,6 +142,22 @@ public class ApplicationImpl implements Application { } } + @Override + public TBoxReasonerModule getTBoxReasonerModule() { + return tboxReasonerModule; + } + + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTBoxReasonerModule") + public void setTBoxReasonerModule(TBoxReasonerModule module) { + if (tboxReasonerModule == null) { + tboxReasonerModule = module; + } else { + throw new IllegalStateException( + "Configuration includes multiple intances of TBoxReasonerModule: " + + tboxReasonerModule + ", and " + module); + } + } + @Validation public void validate() throws Exception { if (searchEngine == null) { @@ -162,6 +180,10 @@ public class ApplicationImpl implements Application { throw new IllegalStateException( "Configuration did not include a ConfigurationTripleSource."); } + if (tboxReasonerModule == null) { + throw new IllegalStateException( + "Configuration did not include a TBoxReasonerModule."); + } } @Override @@ -244,4 +266,30 @@ public class ApplicationImpl implements Application { } } + // ---------------------------------------------------------------------- + // Setup the reasoners. + // + // This must happen after the FileGraphSetup. + // ---------------------------------------------------------------------- + + public static class ReasonersSetup implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext ctx = sce.getServletContext(); + Application app = ApplicationUtils.instance(); + StartupStatus ss = StartupStatus.getBean(ctx); + ComponentStartupStatus css = new ComponentStartupStatusImpl(this, + ss); + + TBoxReasonerModule tboxReasoner = app.getTBoxReasonerModule(); + tboxReasoner.startup(app, css); + ss.info(this, "Started the TBoxReasonerModule: " + tboxReasoner); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + Application app = ApplicationUtils.instance(); + app.getTBoxReasonerModule().shutdown(app); + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BaseSiteAdminController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BaseSiteAdminController.java index 5bcdbc53e..450a9cb7b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BaseSiteAdminController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/BaseSiteAdminController.java @@ -15,6 +15,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vedit.beans.Option; import edu.cornell.mannlib.vedit.util.FormUtils; +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest; @@ -24,7 +25,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMa import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -import edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.PelletListener; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerStatus; import edu.cornell.mannlib.vitro.webapp.search.controller.IndexController; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; @@ -159,26 +160,23 @@ public class BaseSiteAdminController extends FreemarkerHttpServlet { if (PolicyHelper.isAuthorizedForActions(vreq, SimplePermission.EDIT_ONTOLOGY.ACTION)) { - String pelletError = null; - String pelletExplanation = null; - Object plObj = getServletContext().getAttribute("pelletListener"); - if ( (plObj != null) && (plObj instanceof PelletListener) ) { - PelletListener pelletListener = (PelletListener) plObj; - if (!pelletListener.isConsistent()) { - pelletError = "INCONSISTENT ONTOLOGY: reasoning halted."; - pelletExplanation = pelletListener.getExplanation(); - } else if ( pelletListener.isInErrorState() ) { - pelletError = "An error occurred during reasoning. Reasoning has been halted. See error log for details."; - } + String error = null; + String explanation = null; + TBoxReasonerStatus status = ApplicationUtils.instance().getTBoxReasonerModule().getStatus(); + if (!status.isConsistent()) { + error = "INCONSISTENT ONTOLOGY: reasoning halted."; + explanation = status.getExplanation(); + } else if ( status.isInErrorState() ) { + error = "An error occurred during reasoning. Reasoning has been halted. See error log for details."; } - if (pelletError != null) { - Map pellet = new HashMap(); - pellet.put("error", pelletError); - if (pelletExplanation != null) { - pellet.put("explanation", pelletExplanation); + if (error != null) { + Map tboxReasonerStatus = new HashMap(); + tboxReasonerStatus.put("error", error); + if (explanation != null) { + tboxReasonerStatus.put("explanation", explanation); } - map.put("pellet", pellet); + map.put("tboxReasonerStatus", tboxReasonerStatus); } Map urls = new HashMap(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/jena/JenaAdminActions.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/jena/JenaAdminActions.java index 08b007fef..65e164714 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/jena/JenaAdminActions.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/jena/JenaAdminActions.java @@ -44,10 +44,12 @@ import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; import edu.cornell.mannlib.vedit.controller.BaseEditController; +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule; public class JenaAdminActions extends BaseEditController { @@ -190,9 +192,8 @@ public class JenaAdminActions extends BaseEditController { } private void printRestrictions() { - OntModel memoryModel = (OntModel) getServletContext().getAttribute("pelletOntModel"); - for (Restriction rest : memoryModel.listRestrictions().toList() ) { - //System.out.println(); + TBoxReasonerModule reasoner = ApplicationUtils.instance().getTBoxReasonerModule(); + for (Restriction rest : reasoner.listRestrictions() ) { if (rest.isAllValuesFromRestriction()) { log.trace("All values from: "); AllValuesFromRestriction avfr = rest.asAllValuesFromRestriction(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java index 1e0d7a384..5afeca4d8 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/DataPropertyDaoJena.java @@ -41,6 +41,7 @@ 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.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean; import edu.cornell.mannlib.vitro.webapp.beans.DataProperty; import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; @@ -51,7 +52,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyDao; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; -import edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.PelletListener; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerStatus; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; public class DataPropertyDaoJena extends PropertyDaoJena implements @@ -357,10 +358,8 @@ public class DataPropertyDaoJena extends PropertyDaoJena implements } protected boolean reasoningAvailable() { - PelletListener pl = getWebappDaoFactory().getPelletListener(); - return !( - ( pl == null || !pl.isConsistent() || pl.isInErrorState() ) - ); + TBoxReasonerStatus status = ApplicationUtils.instance().getTBoxReasonerModule().getStatus(); + return status.isConsistent() && !status.isInErrorState(); } private String getRequiredDatatypeURI(Individual individual, DataProperty dataprop, List vclassURIs) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java index 085c4dc4e..ab3890d12 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java @@ -18,7 +18,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jena.iri.IRI; import org.apache.jena.iri.IRIFactory; -import org.mindswap.pellet.jena.vocabulary.SWRL; import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; import com.hp.hpl.jena.graph.Node; @@ -55,6 +54,8 @@ public class JenaBaseDao extends JenaBaseDaoCon { public static final boolean KEEP_ONLY_IF_TRUE = true; //used for updatePropertyBooleanValue() public static final boolean KEEP_ONLY_IF_FALSE = false; //used for updatePropertyBooleanValue() + private static final String SWRL_IMP = "http://www.w3.org/2003/11/swrl#Imp"; + protected static final Log log = LogFactory.getLog(JenaBaseDao.class.getName()); /* ******************* static constants ****************** */ @@ -1090,7 +1091,7 @@ public class JenaBaseDao extends JenaBaseDaoCon { } public void removeRulesMentioningResource(Resource res, OntModel ontModel) { - Iterator impIt = ontModel.listSubjectsWithProperty(RDF.type, SWRL.Imp); + Iterator impIt = ontModel.listSubjectsWithProperty(RDF.type, SWRL_IMP); while (impIt.hasNext()) { Resource imp = impIt.next(); boolean removeMe = false; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java index a74cada20..4114c7fbd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/WebappDaoFactoryJena.java @@ -47,7 +47,6 @@ import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactoryConfig; -import edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.PelletListener; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel; @@ -70,8 +69,6 @@ public class WebappDaoFactoryJena implements WebappDaoFactory { protected WebappDaoFactoryConfig config; - protected PelletListener pelletListener; - protected String userURI; private Map properties = new HashMap(); @@ -238,18 +235,6 @@ public class WebappDaoFactoryJena implements WebappDaoFactory { return config.getNonUserNamespaces(); } - /** - * This enables the WebappDaoFactory to check the status of a reasoner. - * This will likely be refactored in future releases. - */ - public void setPelletListener(PelletListener pl) { - this.pelletListener = pl; - } - - public PelletListener getPelletListener() { - return this.pelletListener; - } - @Override public List getCommentsForResource(String resourceURI) { List commentList = new LinkedList(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPattern.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPattern.java deleted file mode 100644 index 001ecee14..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPattern.java +++ /dev/null @@ -1,52 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.dao.jena.pellet; - -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.Resource; - -public class ObjectPropertyStatementPattern { - - private Resource subject = null; - private Property predicate = null; - private Resource object = null; - - public ObjectPropertyStatementPattern(Resource subject, Property predicate, Resource object) { - this.subject = subject; - this.predicate = predicate; - this.object = object; - } - - public Resource getSubject() { - return this.subject; - } - public Property getPredicate() { - return this.predicate; - } - public Resource getObject() { - return this.object; - } - - public boolean matches(ObjectPropertyStatementPattern p2) { - boolean sMatch = false; - boolean pMatch = false; - boolean oMatch = false; - if (this.getSubject() == null || p2.getSubject()==null) { - sMatch = true; // (this.getSubject() == null && p2.getSubject() == null); - } else { - sMatch = (this.getSubject().equals(p2.getSubject())); - } - if (this.getPredicate() == null || p2.getPredicate()==null) { - pMatch = true; // (this.getPredicate() == null && p2.getPredicate() == null); - } else { - pMatch = (this.getPredicate().equals(p2.getPredicate())); - } - if (this.getObject() == null || p2.getObject()==null) { - oMatch = true ; // (this.getObject() == null && p2.getObject() == null); - } else { - oMatch = (this.getObject().equals(p2.getObject())); - } - return (sMatch && pMatch && oMatch); - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPatternFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPatternFactory.java deleted file mode 100644 index 57fb8f900..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ObjectPropertyStatementPatternFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.dao.jena.pellet; - -import com.hp.hpl.jena.rdf.model.Property; -import com.hp.hpl.jena.rdf.model.Resource; - -public class ObjectPropertyStatementPatternFactory { - - //private static Set patternSet = new HashSet(); - - public static ObjectPropertyStatementPattern getPattern(Resource subject, Property predicate, Resource object) { - //for (Iterator i = patternSet.iterator(); i.hasNext(); ) { - // ObjectPropertyStatementPattern pat = i.next(); - // if ( ( (pat.getSubject()==null && subject==null) || (pat.getSubject().equals(subject)) ) - // && ( (pat.getPredicate()==null && predicate==null) || (pat.getPredicate().equals(predicate)) ) - // && ( (pat.getObject()==null && object==null) || (pat.getObject().equals(object)) ) ) { - // return pat; - // } - //} - ObjectPropertyStatementPattern newPat = new ObjectPropertyStatementPattern(subject,predicate,object); - //patternSet.add(newPat); - return newPat; - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/PelletListener.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/PelletListener.java deleted file mode 100644 index 47634ddba..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/PelletListener.java +++ /dev/null @@ -1,732 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.dao.jena.pellet; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.mindswap.pellet.exceptions.InconsistentOntologyException; -import org.mindswap.pellet.jena.PelletInfGraph; -import org.mindswap.pellet.jena.PelletReasonerFactory; - -import com.hp.hpl.jena.ontology.DatatypeProperty; -import com.hp.hpl.jena.ontology.ObjectProperty; -import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelChangedListener; -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.Statement; -import com.hp.hpl.jena.rdf.model.StmtIterator; -import com.hp.hpl.jena.shared.Lock; -import com.hp.hpl.jena.util.iterator.ClosableIterator; -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.VitroVocabulary; -import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; - -public class PelletListener implements ModelChangedListener { - - private static final Log log = LogFactory.getLog(PelletListener.class.getName()); - private boolean isReasoning = false; - private boolean isSynchronizing = false; - private boolean dirty = false; - - private OntModel pelletModel; - private OntModel fullModel; - private OntModel mainModel; - private Model inferenceModel; - private ReasonerConfiguration reasonerConfiguration; - private Set inferenceDrivingPatternAllowSet; - private Set inferenceDrivingPatternDenySet; - private Set inferenceReceivingPatternAllowSet; - - private Map> inferenceDrivingPatternMap; - - private Model additionModel; - private Model removalModel; - - private Model deletedObjectProperties; - private Model deletedDataProperties; - - private boolean pipeOpen; - - private boolean isConsistent = true; - private boolean inErrorState = false; - private String explanation = ""; - - public boolean isConsistent() { - return this.isConsistent; - } - - public String getExplanation() { - return this.explanation; - } - - public boolean isInErrorState() { - return this.inErrorState; - } - - public boolean isReasoning() { - return this.isReasoning; - } - - public void closePipe() { - pipeOpen = false; - } - - public void openPipe() { - pipeOpen = true; - } - - public synchronized boolean checkAndStartReasoning(){ - if( this.isReasoning ) - return false; - else{ - this.isReasoning = true; - return true; - } - } - - public synchronized void endReasoning() { - this.isReasoning = false; - } - - public boolean isDirty() { - return this.dirty; - } - - public void setDirty(boolean dirt) { - this.dirty = dirt; - } - - private int inferenceRounds = 0; - - private boolean foreground = false; - private static final boolean FOREGROUND = true; - private static final boolean BACKGROUND = false; - private static final boolean DONT_SKIP_INITIAL_REASONING = false; - - public PelletListener(OntModel fullModel, OntModel model, Model inferenceModel, ReasonerConfiguration reasonerConfiguration) { - this(fullModel, model, inferenceModel, reasonerConfiguration, BACKGROUND); - } - - public PelletListener(OntModel fullModel, OntModel model, Model inferenceModel, ReasonerConfiguration reasonerConfiguration, boolean foreground) { - this(fullModel, model, inferenceModel, reasonerConfiguration, foreground, DONT_SKIP_INITIAL_REASONING); - } - - public PelletListener(OntModel fullModel, OntModel model, Model inferenceModel, ReasonerConfiguration reasonerConfiguration, boolean foreground, boolean skipReasoningUponInitialization) { - this.pelletModel = ModelFactory.createOntologyModel(reasonerConfiguration.getOntModelSpec()); - this.fullModel = fullModel; - this.mainModel = model; - this.inferenceModel = inferenceModel; - if (this.inferenceModel == null) { - log.trace("Inference model is null"); - } - this.reasonerConfiguration = reasonerConfiguration; - this.inferenceDrivingPatternAllowSet = reasonerConfiguration.getInferenceDrivingPatternAllowSet(); - this.inferenceDrivingPatternDenySet = reasonerConfiguration.getInferenceDrivingPatternDenySet(); - this.inferenceReceivingPatternAllowSet = reasonerConfiguration.getInferenceReceivingPatternAllowSet(); - - if (this.inferenceDrivingPatternAllowSet != null) { - this.inferenceDrivingPatternMap = new HashMap>(); - for (Iterator i = inferenceDrivingPatternAllowSet.iterator(); i.hasNext();) { - ObjectPropertyStatementPattern pat = i.next(); - Property p = pat.getPredicate(); - List patList = inferenceDrivingPatternMap.get(p); - if (patList == null) { - patList = new LinkedList(); - patList.add(pat); - inferenceDrivingPatternMap.put(p, patList); - } else { - patList.add(pat); - } - } - } - this.pipeOpen = true; - this.additionModel = ModelFactory.createDefaultModel(); - this.removalModel = ModelFactory.createDefaultModel(); - this.deletedObjectProperties = ModelFactory.createDefaultModel(); - this.deletedDataProperties = ModelFactory.createDefaultModel(); - this.mainModel.enterCriticalSection(Lock.READ); - try { - for (ObjectPropertyStatementPattern pat : this.inferenceDrivingPatternAllowSet) { - addedStatements(mainModel.listStatements((Resource) null, pat.getPredicate(), (RDFNode) null)); - } - if (!skipReasoningUponInitialization) { - this.foreground = foreground; - notifyEvent(null,new EditEvent(null,false)); - } else if (inferenceModel.size() == 0){ - foreground = true; - notifyEvent(null,new EditEvent(null,false)); - this.foreground = foreground; - } - } finally { - this.mainModel.leaveCriticalSection(); - } - - this.fullModel.getBaseModel().register(this); - this.mainModel.getBaseModel().register(this); - } - - private class InferenceGetter implements Runnable { - - private PelletListener pelletListener; - - public InferenceGetter(PelletListener pelletListener) { - this.pelletListener = pelletListener; - } - - public void run() { - while (pelletListener.isDirty()) { - //pipeOpen = false; - try { - pelletListener.setDirty(false); - inferenceRounds++; - log.info("Getting new inferences"); - long startTime = System.currentTimeMillis(); - LinkedList irpl = new LinkedList(); - - if (inferenceReceivingPatternAllowSet != null) { - irpl.addAll(inferenceReceivingPatternAllowSet); - } else { - irpl.add(ObjectPropertyStatementPatternFactory.getPattern(null,null,null)); - } - - if (reasonerConfiguration.getQueryForAllObjectProperties()) { - pelletModel.enterCriticalSection(Lock.READ); - try { - ClosableIterator closeIt = pelletModel.listObjectProperties(); - try { - for (Iterator objPropIt = closeIt; objPropIt.hasNext();) { - ObjectProperty objProp = (ObjectProperty) objPropIt.next(); - if ( !("http://www.w3.org/2002/07/owl#".equals(objProp.getNameSpace())) ) { - irpl.add(ObjectPropertyStatementPatternFactory.getPattern(null,objProp,null)); - } - } - } finally { - closeIt.close(); - } - } finally { - pelletModel.leaveCriticalSection(); - } - deletedObjectProperties.enterCriticalSection(Lock.WRITE); - try { - ClosableIterator sit = deletedObjectProperties.listSubjects(); - try { - while (sit.hasNext()) { - Resource subj = (Resource) sit.next(); - irpl.add(ObjectPropertyStatementPatternFactory.getPattern(null,ResourceFactory.createProperty(subj.getURI()),null)); - } - } finally { - sit.close(); - } - deletedObjectProperties.removeAll(); - } finally { - deletedObjectProperties.leaveCriticalSection(); - } - } - - if (reasonerConfiguration.getQueryForAllDatatypeProperties()) { - pelletModel.enterCriticalSection(Lock.READ); - try { - ClosableIterator closeIt = pelletModel.listDatatypeProperties(); - try { - for (Iterator dataPropIt = closeIt; dataPropIt.hasNext();) { - DatatypeProperty dataProp = (DatatypeProperty) dataPropIt.next(); - if ( !("http://www.w3.org/2002/07/owl#".equals(dataProp.getNameSpace())) ) { - // TODO: THIS WILL WORK, BUT NEED TO GENERALIZE THE PATTERN CLASSES - irpl.add(ObjectPropertyStatementPatternFactory.getPattern(null,dataProp,null)); - } - } - } finally { - closeIt.close(); - } - } finally { - pelletModel.leaveCriticalSection(); - } - deletedDataProperties.enterCriticalSection(Lock.WRITE); - try { - ClosableIterator sit = deletedDataProperties.listSubjects(); - try { - while (sit.hasNext()) { - Resource subj = (Resource) sit.next(); - irpl.add(ObjectPropertyStatementPatternFactory.getPattern(null,ResourceFactory.createProperty(subj.getURI()),null)); - } - } finally { - sit.close(); - } - deletedDataProperties.removeAll(); - } finally { - deletedDataProperties.leaveCriticalSection(); - } - } - - int addCount = 0; - int retractCount = 0; - - // force new reasoner (disabled) - if (false && !reasonerConfiguration.isIncrementalReasoningEnabled()) { - Model baseModel = pelletModel.getBaseModel(); - pelletModel = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); - pelletModel.getDocumentManager().setProcessImports(false); - pelletModel.add(baseModel); - } - - pelletModel.enterCriticalSection(Lock.WRITE); - try { - pelletModel.rebind(); - pelletModel.prepare(); - } finally { - pelletModel.leaveCriticalSection(); - } - - for (Iterator patIt = irpl.iterator(); patIt.hasNext(); ) { - ObjectPropertyStatementPattern pat = patIt.next(); - - if (log.isDebugEnabled()) { - String subjStr = (pat.getSubject() != null) ? pat.getSubject().getURI() : "*"; - String predStr = (pat.getPredicate() != null) ? pat.getPredicate().getURI() : "*"; - String objStr = (pat.getObject() != null) ? pat.getObject().getURI() : "*"; - log.debug("Querying for "+subjStr+" : "+predStr+" : "+objStr); - } - - Model tempModel = ModelFactory.createDefaultModel(); - - pelletModel.enterCriticalSection(Lock.READ); - try { - - ClosableIterator ci = pelletModel.listStatements(pat.getSubject(),pat.getPredicate(),pat.getObject()); - try { - for (ClosableIterator i=ci; i.hasNext();) { - Statement stmt = (Statement) i.next(); - - boolean reject = false; - - // this next part is only needed if we're using Jena's OWL reasoner instead of actually using Pellet - try { - if ( ( ((Resource)stmt.getObject()).equals(RDFS.Resource) ) ) { - reject = true; - } else if ( ( stmt.getSubject().equals(OWL.Nothing) ) ) { - reject = true; - } else if ( ( stmt.getObject().equals(OWL.Nothing) ) ) { - reject = true; - } - } catch (Exception e) {} - - if (!reject) { - tempModel.add(stmt); - - boolean fullModelContainsStatement = false; - fullModel.enterCriticalSection(Lock.READ); - try { - fullModelContainsStatement = fullModel.contains(stmt); - } finally { - fullModel.leaveCriticalSection(); - } - - if (!fullModelContainsStatement) { - // in theory we should be able to lock only the inference model, but I'm not sure yet if Jena propagates the locking upward - fullModel.enterCriticalSection(Lock.WRITE); - closePipe(); - try { - inferenceModel.add(stmt); - addCount++; - } finally { - openPipe(); - fullModel.leaveCriticalSection(); - } - } - - } - } - } finally { - ci.close(); - } - } finally { - pelletModel.leaveCriticalSection(); - } - - // now we see what's in the inference model that isn't in the temp model and remove it - - try { - Queue localRemovalQueue = new LinkedList(); - inferenceModel.enterCriticalSection(Lock.READ); - try { - ClosableIterator ci = inferenceModel.listStatements(pat.getSubject(),pat.getPredicate(),pat.getObject()); - try { - for (ClosableIterator i=ci; i.hasNext();) { - Statement stmt = (Statement) i.next(); - if (!tempModel.contains(stmt)) { - localRemovalQueue.add(stmt); - } - } - } finally { - ci.close(); - } - } finally { - inferenceModel.leaveCriticalSection(); - } - for (Iterator i = localRemovalQueue.iterator(); i.hasNext(); ) { - fullModel.enterCriticalSection(Lock.WRITE); - closePipe(); - try { - retractCount++; - inferenceModel.remove(i.next()); - } finally { - openPipe(); - fullModel.leaveCriticalSection(); - } - } - - localRemovalQueue.clear(); - } catch (Exception e) { - log.error("Error getting inferences", e); - } - tempModel = null; - } - this.pelletListener.isConsistent = true; - this.pelletListener.inErrorState = false; - this.pelletListener.explanation = ""; - if (log.isDebugEnabled()) { - log.info("Added "+addCount+" statements entailed by assertions"); - log.info("Retracted "+retractCount+" statements no longer entailed by assertions"); - log.info("Done getting new inferences: "+(System.currentTimeMillis()-startTime)/1000+" seconds"); - } - } catch (InconsistentOntologyException ioe) { - this.pelletListener.isConsistent = false; - String explanation = ((PelletInfGraph)pelletModel.getGraph()).getKB().getExplanation(); - this.pelletListener.explanation = explanation; - log.error(ioe); - log.error(explanation); - } catch (Exception e) { - this.pelletListener.inErrorState = true; - log.error("Exception during inference", e); - } finally { - pelletListener.endReasoning(); - } - } - } - - } - - private void getInferences() { - this.setDirty(true); - if ( this.checkAndStartReasoning() ){ - if (foreground) { - (new InferenceGetter(this)).run(); - } else { - new Thread(new InferenceGetter(this), "PelletListener.InferenceGetter").start(); - } - } - } - - // TODO: These next two methods are really ugly; I need to refactor them to remove redundancy. - - private void tryAdd(Statement stmt) { - boolean sendToPellet = false; - if ( pipeOpen && reasonerConfiguration.getReasonOnAllDatatypePropertyStatements() && stmt.getObject().isLiteral() ) { - sendToPellet = true; - } else - if ( pipeOpen && hasCardinalityPredicate(stmt) ) { // see comment on this method - sendToPellet = true; - } else - if ( (stmt.getObject().isResource()) && !((stmt.getPredicate().getURI().indexOf(VitroVocabulary.vitroURI)==0)) ) { - if (pipeOpen) { - sendToPellet = false; - boolean denied = false; - ObjectPropertyStatementPattern stPat = ObjectPropertyStatementPatternFactory.getPattern(stmt.getSubject(), stmt.getPredicate(), (Resource) stmt.getObject()); - if (inferenceDrivingPatternDenySet != null) { - for (Iterator i = inferenceDrivingPatternDenySet.iterator(); i.hasNext(); ){ - ObjectPropertyStatementPattern pat = i.next(); - if (pat.matches(stPat)) { - denied = true; - break; - } - } - } - if (!denied) { - if (inferenceDrivingPatternAllowSet==null) { - sendToPellet = true; - } else { - // TODO: O(1) implementation of this - List patList = this.inferenceDrivingPatternMap.get(stmt.getPredicate()); - if (patList != null) { - for (Iterator i = patList.iterator(); i.hasNext(); ){ - ObjectPropertyStatementPattern pat = i.next(); - if (pat.matches(stPat)) { - sendToPellet = true; - break; - } - } - } - } - } - - } - } - if (sendToPellet) { - //long startTime = System.currentTimeMillis(); - String valueStr = (stmt.getObject().isResource()) ? ((Resource)stmt.getObject()).getLocalName() : ((Literal)stmt.getObject()).getLexicalForm(); - if ( log.isDebugEnabled() ) { - log.debug( "Adding to Pellet: " + renderStatement( stmt ) ); - } - additionModel.enterCriticalSection(Lock.WRITE); - try { - additionModel.add(stmt); - } finally { - additionModel.leaveCriticalSection(); - } - } else { - if ( log.isDebugEnabled() ) { - log.debug( "Not adding to Pellet: " + renderStatement( stmt ) ); - } - } - } - - - private void tryRemove(Statement stmt) { - boolean removeFromPellet = false; - if ( pipeOpen && reasonerConfiguration.getReasonOnAllDatatypePropertyStatements() && stmt.getObject().isLiteral() ) { - removeFromPellet = true; - } else - if ( pipeOpen && hasCardinalityPredicate(stmt) ) { // see comment on this method - removeFromPellet = true; - } else - if ( stmt.getObject().isResource() ) { - if (pipeOpen) { - if (reasonerConfiguration.getQueryForAllObjectProperties() && stmt.getPredicate().equals(RDF.type) && stmt.getObject().equals(OWL.ObjectProperty)) { - deletedObjectProperties.enterCriticalSection(Lock.WRITE); - try { - deletedObjectProperties.add(stmt); - } finally { - deletedObjectProperties.leaveCriticalSection(); - } - } - if (reasonerConfiguration.getQueryForAllDatatypeProperties() && stmt.getPredicate().equals(RDF.type) && stmt.getObject().equals(OWL.DatatypeProperty)) { - deletedDataProperties.enterCriticalSection(Lock.WRITE); - try{ - deletedDataProperties.add(stmt); - } finally { - deletedDataProperties.leaveCriticalSection(); - } - } - removeFromPellet = false; - boolean denied = false; - ObjectPropertyStatementPattern stPat = ObjectPropertyStatementPatternFactory.getPattern(stmt.getSubject(), stmt.getPredicate(), (Resource) stmt.getObject()); - if (inferenceDrivingPatternDenySet != null) { - for (Iterator i = inferenceDrivingPatternDenySet.iterator(); i.hasNext(); ){ - ObjectPropertyStatementPattern pat = i.next(); - if (pat.matches(stPat)) { - denied = true; - break; - } - } - } - if (!denied) { - if (inferenceDrivingPatternAllowSet==null) { - removeFromPellet = true; - } else { - // TODO: O(1) implementation of this - List patList = this.inferenceDrivingPatternMap.get(stmt.getPredicate()); - if (patList != null) { - for (Iterator i = patList.iterator(); i.hasNext(); ){ - ObjectPropertyStatementPattern pat = i.next(); - if (pat.matches(stPat)) { - removeFromPellet = true; - break; - } - } - } - } - } - } - } - if (removeFromPellet) { - String valueStr = (stmt.getObject().isResource()) ? ((Resource)stmt.getObject()).getLocalName() : ((Literal)stmt.getObject()).getLexicalForm(); - log.info("Removing from Pellet: "+stmt.getSubject().getLocalName()+" "+stmt.getPredicate().getLocalName()+" "+valueStr); - removalModel.enterCriticalSection(Lock.WRITE); - try { - removalModel.add(stmt); - } finally { - removalModel.leaveCriticalSection(); - } - } - } - - // The pattern matching stuff needs to get reworked. - // It originally assumed that only resources would be in object - // position, but cardinality axioms will have e.g. nonNegativeIntegers. - // This is a temporary workaround: all cardinality statements will - // be exposed to Pellet, regardless of configuration patterns. - private boolean hasCardinalityPredicate(Statement stmt) { - return ( - stmt.getPredicate().equals(OWL.cardinality) || - stmt.getPredicate().equals(OWL.minCardinality) || - stmt.getPredicate().equals(OWL.maxCardinality) - ) ; - } - - - public void addedStatement(Statement arg0) { - tryAdd(arg0); - } - - - public void addedStatements(Statement[] arg0) { - for (int i=0; i 0) || (removalModel.size()>0) ) { - if (!isSynchronizing) { - if (foreground) { - log.debug("Running Pellet in foreground."); - (new PelletSynchronizer()).run(); - } else { - log.debug("Running Pellet in background."); - new Thread(new PelletSynchronizer(), "PelletListener.PelletSynchronizer").start(); - } - } - } - } - } - } - - private class PelletSynchronizer implements Runnable { - public void run() { - try { - isSynchronizing = true; - while (removalModel.size()>0 || additionModel.size()>0) { - Model tempModel = ModelFactory.createDefaultModel(); - removalModel.enterCriticalSection(Lock.WRITE); - try { - tempModel.add(removalModel); - removalModel.removeAll(); - } finally { - removalModel.leaveCriticalSection(); - } - pelletModel.enterCriticalSection(Lock.WRITE); - try { - pelletModel.remove(tempModel); - } finally { - pelletModel.leaveCriticalSection(); - } - tempModel.removeAll(); - additionModel.enterCriticalSection(Lock.WRITE); - try { - tempModel.add(additionModel); - additionModel.removeAll(); - } finally { - additionModel.leaveCriticalSection(); - } - pelletModel.enterCriticalSection(Lock.WRITE); - try { - pelletModel.add(tempModel); - } finally { - pelletModel.leaveCriticalSection(); - } - tempModel = null; - - getInferences(); - - } - } finally { - isSynchronizing = false; - } - } - } - - public void removedStatement(Statement arg0) { - tryRemove(arg0); - } - - - public void removedStatements(Statement[] arg0) { - for (int i=0; i addWhenMissingInference( String classUri , WebappDaoFactory wDaoFact ){ - boolean inferenceAvailable = isReasoningAvailable(wDaoFact); + boolean inferenceAvailable = isReasoningAvailable(); Map individualMap = new HashMap(); if ( !inferenceAvailable ) { for (String subclassURI : wDaoFact.getVClassDao().getAllSubClassURIs(classUri)) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/Application.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/Application.java index 2708634f3..7d875f707 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/Application.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/Application.java @@ -8,6 +8,7 @@ import edu.cornell.mannlib.vitro.webapp.application.VitroHomeDirectory; import edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage; import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ConfigurationTripleSource; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource; @@ -29,6 +30,8 @@ public interface Application { ConfigurationTripleSource getConfigurationTripleSource(); + TBoxReasonerModule getTBoxReasonerModule(); + void shutdown(); public interface Component { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerModule.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerModule.java new file mode 100644 index 000000000..3110d9960 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerModule.java @@ -0,0 +1,29 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner; + +import java.util.List; + +import com.hp.hpl.jena.ontology.Restriction; + +import edu.cornell.mannlib.vitro.webapp.modules.Application; + +/** + * A wrapper around the TBox reasoner + */ +public interface TBoxReasonerModule extends Application.Module { + /** + * What is the TBox reasoner doing now? + */ + TBoxReasonerStatus getStatus(); + + /** + * What restrictions are currently in the reasoner's internal model? + */ + List listRestrictions(); + + /** + * Wait until the TBox reasoner becomes quiet. + */ + void waitForTBoxReasoning(); +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerStatus.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerStatus.java new file mode 100644 index 000000000..d799ff8f9 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modules/tboxreasoner/TBoxReasonerStatus.java @@ -0,0 +1,29 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner; + +/** + * What is the current state of the TBox reasoner? + */ +public interface TBoxReasonerStatus { + /** + * Is reasoning in progress based on changes to the TBox? + */ + boolean isReasoning(); + + /** + * Is the TBox free of inconsistency? + */ + boolean isConsistent(); + + /** + * Did the reasoner fail in its most recent attempt? + */ + boolean isInErrorState(); + + /** + * A description of the error state, or an empty string (never null). + */ + String getExplanation(); + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java index 4fbd5d8c2..8e4cc757c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/FileGraphSetup.java @@ -97,13 +97,6 @@ public class FileGraphSetup implements ServletContextListener { OntDocumentManager.getInstance().setProcessImports(false); } - /* - if (isUpdateRequired(ctx)) { - log.info("mostSpecificType will be computed because a knowledge base migration was performed." ); - SimpleReasonerSetup.setMSTComputeRequired(ctx); - } else - */ - if ( (aboxChanged || tboxChanged) && !isUpdateRequired(ctx)) { log.info("a full recompute of the Abox will be performed because" + " the filegraph abox(s) and/or tbox(s) have changed or are being read for the first time." ); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/SimpleReasonerSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/SimpleReasonerSetup.java index 08d3b5cc3..3422eccff 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/SimpleReasonerSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/SimpleReasonerSetup.java @@ -21,12 +21,7 @@ import org.apache.commons.logging.LogFactory; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.query.Dataset; import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.vocabulary.OWL; -import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactoryJena; -import edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.PelletListener; -import edu.cornell.mannlib.vitro.webapp.dao.jena.pellet.ReasonerConfiguration; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; @@ -51,41 +46,10 @@ public class SimpleReasonerSetup implements ServletContextListener { ServletContext ctx = sce.getServletContext(); try { - // set up Pellet reasoning for the TBox OntModel tboxAssertionsModel = ModelAccess.on(ctx).getOntModel(ModelNames.TBOX_ASSERTIONS); OntModel tboxInferencesModel = ModelAccess.on(ctx).getOntModel(ModelNames.TBOX_INFERENCES); OntModel tboxUnionModel = ModelAccess.on(ctx).getOntModel(ModelNames.TBOX_UNION); - log.debug("tboxAssertionsModel=" + tboxAssertionsModel); - log.debug("tboxInferencesModel=" + tboxInferencesModel); - log.debug("tboxUnionModel=" + tboxUnionModel); - WebappDaoFactory wadf = ModelAccess.on(ctx).getWebappDaoFactory(); - - if (!tboxAssertionsModel.getProfile().NAMESPACE().equals(OWL.NAMESPACE.getNameSpace())) { - log.error("Not connecting Pellet reasoner - the TBox assertions model is not an OWL model"); - return; - } - - // Set various Pellet options for incremental consistency checking, etc. - //PelletOptions.DL_SAFE_RULES = true; - //PelletOptions.USE_COMPLETION_QUEUE = true; - //PelletOptions.USE_TRACING = true; - //PelletOptions.TRACK_BRANCH_EFFECTS = true; - //PelletOptions.USE_INCREMENTAL_CONSISTENCY = true; - //PelletOptions.USE_INCREMENTAL_DELETION = true; - - PelletListener pelletListener = new PelletListener(tboxUnionModel,tboxAssertionsModel,tboxInferencesModel,ReasonerConfiguration.DEFAULT); - sce.getServletContext().setAttribute("pelletListener",pelletListener); - sce.getServletContext().setAttribute("pelletOntModel", pelletListener.getPelletModel()); - - if (wadf instanceof WebappDaoFactoryJena) { - ((WebappDaoFactoryJena) wadf).setPelletListener(pelletListener); - } - - log.info("Pellet reasoner connected for the TBox"); - - waitForTBoxReasoning(sce); - // set up simple reasoning for the ABox RDFService rdfService = ModelAccess.on(ctx).getRDFService(); @@ -139,23 +103,6 @@ public class SimpleReasonerSetup implements ServletContextListener { } } - public static void waitForTBoxReasoning(ServletContextEvent sce) - throws InterruptedException { - PelletListener pelletListener = (PelletListener) sce.getServletContext().getAttribute("pelletListener"); - if (pelletListener == null) { - return ; - } - int sleeps = 0; - // sleep at least once to make sure the TBox reasoning gets started - while ((0 == sleeps) || ((sleeps < 1000) && pelletListener.isReasoning())) { - if (((sleeps - 1) % 10) == 0) { // print message at 10 second intervals - log.info("Waiting for initial TBox reasoning to complete"); - } - Thread.sleep(1000); - sleeps++; - } - } - @Override public void contextDestroyed(ServletContextEvent sce) { log.info("received contextDestroyed notification"); @@ -209,17 +156,6 @@ public class SimpleReasonerSetup implements ServletContextListener { return (RecomputeMode) ctx.getAttribute(RECOMPUTE_REQUIRED_ATTR); } - private static final String MSTCOMPUTE_REQUIRED_ATTR = - SimpleReasonerSetup.class.getName() + ".MSTComputeRequired"; - - public static void setMSTComputeRequired(ServletContext ctx) { - ctx.setAttribute(MSTCOMPUTE_REQUIRED_ATTR, true); - } - - private static boolean isMSTComputeRequired(ServletContext ctx) { - return (ctx.getAttribute(MSTCOMPUTE_REQUIRED_ATTR) != null); - } - /** * Read the names of the plugin classes classes. * @@ -278,7 +214,8 @@ public class SimpleReasonerSetup implements ServletContextListener { this.simpleReasoner = simpleReasoner; } - public void run() { + @Override + public void run() { simpleReasoner.recompute(); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java index d7931035e..3419300d6 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/UpdateKnowledgeBase.java @@ -89,7 +89,11 @@ public class UpdateKnowledgeBase implements ServletContextListener { putReportingPathsIntoSettings(ctx, settings); putNonReportingPathsIntoSettings(ctx, settings); - SimpleReasonerSetup.waitForTBoxReasoning(sce); + try { + ApplicationUtils.instance().getTBoxReasonerModule().waitForTBoxReasoning(); + } catch (Exception e) { + // Should mean that the reasoner is not even started yet. + } WebappDaoFactory wadf = ModelAccess.on(ctx).getWebappDaoFactory(); settings.setDefaultNamespace(wadf.getDefaultNamespace()); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ConfiguredReasonerListener.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ConfiguredReasonerListener.java new file mode 100644 index 000000000..8ce096af4 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ConfiguredReasonerListener.java @@ -0,0 +1,349 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelChangedListener; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; +import com.hp.hpl.jena.vocabulary.OWL; +import com.hp.hpl.jena.vocabulary.RDF; + +import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; +import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; + +/** + * Listens for changes on a model. When a change is announced, it is checked for + * worthiness. If worthy, it is added to a change set. + * + * When an ending EditEvent is received, the current change set is passed along + * to the reasoner driver, and a new change set is begun. + * + * Among the criteria for deciding on worthiness is the driving pattern set. In + * the constructor, a map is made from this set, to reduce the number of tests + * made against each statement. I don't know whether this optimization is + * justified. + * + * It is possible to "suspend" the listener, so it will ignore any changes. This + * is useful when the reasoner itself makes changes to the models, so those + * changes do not trigger additional reasoning. + */ +public class ConfiguredReasonerListener implements ModelChangedListener { + private static final Log log = LogFactory + .getLog(ConfiguredReasonerListener.class); + + private final ReasonerConfiguration reasonerConfiguration; + private final TBoxReasonerDriver reasonerDriver; + + private final DrivingPatternMap drivingPatternMap; + + private final AtomicReference changeSet; + private final AtomicBoolean suspended; + + public ConfiguredReasonerListener( + ReasonerConfiguration reasonerConfiguration, + TBoxReasonerDriver reasonerDriver) { + this.reasonerConfiguration = reasonerConfiguration; + this.reasonerDriver = reasonerDriver; + + this.drivingPatternMap = new DrivingPatternMap( + reasonerConfiguration.getInferenceDrivingPatternAllowSet()); + + this.changeSet = new AtomicReference<>(new TBoxChanges()); + this.suspended = new AtomicBoolean(); + } + + public Suspension suspend() { + if (!suspended.compareAndSet(false, true)) { + throw new IllegalStateException("Listener is already suspended."); + } + return new Suspension(); + } + + public class Suspension implements AutoCloseable { + @Override + public void close() { + boolean wasSuspended = suspended.compareAndSet(true, false); + if (!wasSuspended) { + log.warn("Listener was already not suspended."); + } + } + } + + /** + * Lists of patterns, mapped by their predicates. + */ + public class DrivingPatternMap extends + HashMap> { + + public DrivingPatternMap(Set patternSet) { + if (patternSet != null) { + for (ReasonerStatementPattern pat : patternSet) { + Property p = pat.getPredicate(); + if (!containsKey(p)) { + put(p, new LinkedList()); + } + get(p).add(pat); + } + } + } + } + + // ---------------------------------------------------------------------- + // Implement the ModelChangedListener methods. Delegate to the methods that + // check criteria. + // ---------------------------------------------------------------------- + + @Override + public void addedStatement(Statement s) { + tryAdd(s); + } + + @Override + public void addedStatements(Statement[] statements) { + for (Statement stmt : statements) { + tryAdd(stmt); + } + } + + @Override + public void addedStatements(List statements) { + for (Statement stmt : statements) { + tryAdd(stmt); + } + } + + @Override + public void addedStatements(StmtIterator statements) { + for (Statement stmt : statements.toList()) { + tryAdd(stmt); + } + } + + @Override + public void addedStatements(Model m) { + for (Statement stmt : m.listStatements().toList()) { + tryAdd(stmt); + } + } + + @Override + public void removedStatement(Statement s) { + tryRemove(s); + } + + @Override + public void removedStatements(Statement[] statements) { + for (Statement stmt : statements) { + tryRemove(stmt); + } + } + + @Override + public void removedStatements(List statements) { + for (Statement stmt : statements) { + tryRemove(stmt); + } + } + + @Override + public void removedStatements(StmtIterator statements) { + for (Statement stmt : statements.toList()) { + tryRemove(stmt); + } + } + + @Override + public void removedStatements(Model m) { + for (Statement stmt : m.listStatements().toList()) { + tryRemove(stmt); + } + } + + @Override + public void notifyEvent(Model m, Object event) { + if (event instanceof EditEvent) { + EditEvent ee = (EditEvent) event; + if (!ee.getBegin()) { + TBoxChanges changes = changeSet.getAndSet(new TBoxChanges()); + this.reasonerDriver.runSynchronizer(changes); + } + } + } + + // ---------------------------------------------------------------------- + // Check the criteria to determine whether each addition or removal should + // be passed to the reasoner. + // + // When the listener is suspended, nothing is passed on. + // ---------------------------------------------------------------------- + + public void tryAdd(Statement stmt) { + if (suspended.get()) { + return; + } + + if (isDataProperty(stmt)) { + if (reasonOnAllDataProperties() || hasCardinalityPredicate(stmt)) { + addIt(stmt); + return; + } else { + return; + } + } + + if (predicateIsInVitroNamespace(stmt) + || statementMatchesDenyPattern(stmt)) { + return; + } + + if (thereAreNoDrivingPatterns() || statementMatchesDrivingPattern(stmt)) { + addIt(stmt); + return; + } + } + + public void tryRemove(Statement stmt) { + if (suspended.get()) { + return; + } + + if (isDataProperty(stmt)) { + if (reasonOnAllDataProperties() || hasCardinalityPredicate(stmt)) { + removeIt(stmt); + return; + } else { + return; + } + } + + if (actOnObjectPropertyDeclarations() && declaresObjectProperty(stmt)) { + deleteObjectProperty(stmt); + return; + } + + if (actOnDataPropertyDeclarations() && declaresDataProperty(stmt)) { + deleteDataProperty(stmt); + return; + } + + if (statementMatchesDenyPattern(stmt)) { + return; + } + + if (thereAreNoDrivingPatterns() || statementMatchesDrivingPattern(stmt)) { + removeIt(stmt); + return; + } + } + + private boolean isDataProperty(Statement stmt) { + return stmt.getObject().isLiteral(); + } + + private boolean reasonOnAllDataProperties() { + return reasonerConfiguration.getReasonOnAllDatatypePropertyStatements(); + } + + private boolean predicateIsInVitroNamespace(Statement stmt) { + return stmt.getPredicate().getURI().indexOf(VitroVocabulary.vitroURI) == 0; + } + + private boolean statementMatchesDenyPattern(Statement stmt) { + Set denyPatterns = reasonerConfiguration.inferenceDrivingPatternDenySet; + if (denyPatterns == null) { + return false; + } + + ReasonerStatementPattern stPat = ReasonerStatementPattern + .objectPattern(stmt); + + for (ReasonerStatementPattern pat : denyPatterns) { + if (pat.matches(stPat)) { + return true; + } + } + + return false; + } + + private boolean thereAreNoDrivingPatterns() { + return reasonerConfiguration.inferenceDrivingPatternAllowSet == null; + } + + private boolean statementMatchesDrivingPattern(Statement stmt) { + List drivePatterns = drivingPatternMap + .get(stmt.getPredicate()); + if (drivePatterns == null) { + return false; + } + + ReasonerStatementPattern stPat = ReasonerStatementPattern + .objectPattern(stmt); + + for (ReasonerStatementPattern pat : drivePatterns) { + if (pat.matches(stPat)) { + return true; + } + } + + return false; + } + + private boolean actOnObjectPropertyDeclarations() { + return reasonerConfiguration.getQueryForAllObjectProperties(); + } + + private boolean declaresObjectProperty(Statement stmt) { + return stmt.getPredicate().equals(RDF.type) + && stmt.getObject().equals(OWL.ObjectProperty); + } + + private boolean actOnDataPropertyDeclarations() { + return reasonerConfiguration.getQueryForAllDatatypeProperties(); + } + + private boolean declaresDataProperty(Statement stmt) { + return stmt.getPredicate().equals(RDF.type) + && stmt.getObject().equals(OWL.DatatypeProperty); + } + + private void addIt(Statement stmt) { + changeSet.get().addStatement(stmt); + } + + private void removeIt(Statement stmt) { + changeSet.get().removeStatement(stmt); + } + + private void deleteObjectProperty(Statement stmt) { + changeSet.get().deleteObjectProperty(stmt); + } + + private void deleteDataProperty(Statement stmt) { + changeSet.get().deleteDataProperty(stmt); + } + + // The pattern matching stuff needs to get reworked. + // It originally assumed that only resources would be in object + // position, but cardinality axioms will have e.g. nonNegativeIntegers. + // This is a temporary workaround: all cardinality statements will + // be exposed to Pellet, regardless of configuration patterns. + private boolean hasCardinalityPredicate(Statement stmt) { + return (stmt.getPredicate().equals(OWL.cardinality) + || stmt.getPredicate().equals(OWL.minCardinality) || stmt + .getPredicate().equals(OWL.maxCardinality)); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/InferenceModelUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/InferenceModelUpdater.java new file mode 100644 index 000000000..1fcfaaabe --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/InferenceModelUpdater.java @@ -0,0 +1,119 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Statement; + +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ConfiguredReasonerListener; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ConfiguredReasonerListener.Suspension; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerStatementPattern; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasoner; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableOntModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockedModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockedOntModel; + +/** + * A tool that will adjust the inferences model to match the reasoner model, + * after applying the proper filters to both. + */ +public class InferenceModelUpdater { + private static final Log log = LogFactory + .getLog(InferenceModelUpdater.class); + + private final TBoxReasoner reasoner; + private final LockableModel lockableInferencesModel; + private final LockableOntModel lockableFullModel; + private final ConfiguredReasonerListener listener; + + private int addCount; + private int retractCount; + + public int getAddCount() { + return addCount; + } + + public int getRetractCount() { + return retractCount; + } + + public InferenceModelUpdater(TBoxReasoner reasoner, + LockableModel lockableInferencesModel, + LockableOntModel lockableFullModel, + ConfiguredReasonerListener listener) { + this.reasoner = reasoner; + this.lockableInferencesModel = lockableInferencesModel; + this.lockableFullModel = lockableFullModel; + this.listener = listener; + } + + /** + * Synchronize the inferences model with the reasoner model, with this + * proviso: + * + * If a statement exists anywhere in the full TBox, don't bother adding it + * to the inferences model. + */ + public void update(List patternList) { + List filteredReasonerModel = reasoner + .filterResults(patternList); + addNewInferences(filteredReasonerModel); + removeOldInferences(filterInferencesModel(patternList), + filteredReasonerModel); + log.debug("Added: " + addCount + ", Retracted: " + retractCount); + } + + private void addNewInferences(List filteredReasonerModel) { + for (Statement stmt : filteredReasonerModel) { + if (!fullModelContainsStatement(stmt)) { + try (LockedModel inferenceModel = lockableInferencesModel + .write(); Suspension susp = listener.suspend()) { + inferenceModel.add(stmt); + addCount++; + } + } + } + } + + private boolean fullModelContainsStatement(Statement stmt) { + try (LockedOntModel fullModel = lockableFullModel.read()) { + return fullModel.contains(stmt); + } + } + + private Model filterInferencesModel( + List patternList) { + Model filtered = ModelFactory.createDefaultModel(); + try (LockedModel inferencesModel = lockableInferencesModel.read()) { + for (ReasonerStatementPattern pattern : patternList) { + filtered.add(pattern.matchStatementsFromModel(inferencesModel)); + } + } + log.debug("Filtered inferences model: " + filtered.size()); + return filtered; + } + + private void removeOldInferences(Model filteredInferencesModel, + List filteredReasonerStatements) { + Model filteredReasonerModel = ModelFactory.createDefaultModel(); + filteredReasonerModel.add(filteredReasonerStatements); + + for (Statement stmt : filteredInferencesModel.listStatements().toList()) { + if (!filteredReasonerModel.contains(stmt)) { + try (LockedModel inferenceModel = lockableInferencesModel + .write(); Suspension susp = listener.suspend()) { + retractCount++; + inferenceModel.remove(stmt); + } + } + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/PatternListBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/PatternListBuilder.java new file mode 100644 index 000000000..97574caaf --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/PatternListBuilder.java @@ -0,0 +1,81 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty; + +import java.util.LinkedList; +import java.util.Set; + +import com.hp.hpl.jena.ontology.DatatypeProperty; +import com.hp.hpl.jena.ontology.ObjectProperty; + +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerConfiguration; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerStatementPattern; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxChanges; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasoner; + +/** + * The list of patterns for filtering the models will include: + * + * All patterns specified by the ReasonerConfiguration, + * + * One pattern for each deleted property, to match the use of that property as a + * predicate. + */ +public class PatternListBuilder { + private static final String OWL_NS = "http://www.w3.org/2002/07/owl#"; + + private final ReasonerConfiguration reasonerConfiguration; + private final TBoxReasoner reasoner; + private final TBoxChanges changes; + + public PatternListBuilder(ReasonerConfiguration reasonerConfiguration, + TBoxReasoner reasoner, TBoxChanges changes) { + this.reasonerConfiguration = reasonerConfiguration; + this.reasoner = reasoner; + this.changes = changes; + } + + public LinkedList build() { + LinkedList patterns = new LinkedList<>(); + + Set allowSet = reasonerConfiguration + .getInferenceReceivingPatternAllowSet(); + if (allowSet != null) { + patterns.addAll(allowSet); + } else { + patterns.add(ReasonerStatementPattern.ANY_OBJECT_PROPERTY); + } + + if (reasonerConfiguration.getQueryForAllObjectProperties()) { + for (ObjectProperty objProp : reasoner.listObjectProperties()) { + if (!(OWL_NS.equals(objProp.getNameSpace()))) { + patterns.add(ReasonerStatementPattern + .objectPattern(objProp)); + } + } + + for (String uri : changes.getDeletedObjectPropertyUris()) { + patterns.add(ReasonerStatementPattern + .objectPattern(createProperty(uri))); + } + } + + if (reasonerConfiguration.getQueryForAllDatatypeProperties()) { + for (DatatypeProperty dataProp : reasoner.listDatatypeProperties()) { + if (!(OWL_NS.equals(dataProp.getNameSpace()))) { + // TODO: THIS WILL WORK, BUT NEED TO GENERALIZE THE + // PATTERN CLASSES + patterns.add(ReasonerStatementPattern + .objectPattern(dataProp)); + } + } + for (String uri : changes.getDeletedDataPropertyUris()) { + patterns.add(ReasonerStatementPattern + .objectPattern(createProperty(uri))); + } + } + return patterns; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ReasonerConfiguration.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerConfiguration.java similarity index 52% rename from webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ReasonerConfiguration.java rename to webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerConfiguration.java index 970c14e4d..8f6889ba2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/pellet/ReasonerConfiguration.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerConfiguration.java @@ -1,22 +1,22 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ -package edu.cornell.mannlib.vitro.webapp.dao.jena.pellet; +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; import java.util.HashSet; import java.util.Set; +import org.mindswap.pellet.jena.PelletReasonerFactory; + import com.hp.hpl.jena.ontology.OntModelSpec; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; -import org.mindswap.pellet.jena.PelletReasonerFactory; - public class ReasonerConfiguration { - public Set inferenceDrivingPatternAllowSet; - public Set inferenceDrivingPatternDenySet; - public Set inferenceReceivingPatternAllowSet; + public Set inferenceDrivingPatternAllowSet; + public Set inferenceDrivingPatternDenySet; + public Set inferenceReceivingPatternAllowSet; private boolean queryForAllObjectProperties = false; private boolean incrementalReasoningEnabled = true; @@ -58,34 +58,34 @@ public class ReasonerConfiguration { //ask the reasoner only to classify, realize, and infer disjointWith statements (based on a somewhat incomplete information) DEFAULT = new ReasonerConfiguration(); - HashSet defaultInferenceDrivingPatternAllowSet = new HashSet(); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDF.type,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDFS.subClassOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDFS.subPropertyOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.equivalentClass,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.unionOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.intersectionOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.complementOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.oneOf,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.onProperty,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.someValuesFrom,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.allValuesFrom,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.hasValue,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.minCardinality,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.maxCardinality,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.cardinality,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDF.first,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDF.rest,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.disjointWith,null)); - defaultInferenceDrivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.inverseOf,null)); + HashSet defaultInferenceDrivingPatternAllowSet = new HashSet<>(); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDF.type)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDFS.subClassOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDFS.subPropertyOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.equivalentClass)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.unionOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.intersectionOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.complementOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.oneOf)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.onProperty)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.someValuesFrom)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.allValuesFrom)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.hasValue)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.minCardinality)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.maxCardinality)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.cardinality)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDF.first)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDF.rest)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.disjointWith)); + defaultInferenceDrivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.inverseOf)); DEFAULT.setInferenceDrivingPatternAllowSet(defaultInferenceDrivingPatternAllowSet); - Set defaultInferenceReceivingPatternAllowSet = new HashSet(); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDF.type,null)); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDFS.subClassOf,null)); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,RDFS.subPropertyOf,null)); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.equivalentClass,null)); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.disjointWith,null)); - defaultInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null,OWL.inverseOf,null)); + Set defaultInferenceReceivingPatternAllowSet = new HashSet<>(); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDF.type)); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDFS.subClassOf)); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(RDFS.subPropertyOf)); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.equivalentClass)); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.disjointWith)); + defaultInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.inverseOf)); DEFAULT.setInferenceReceivingPatternAllowSet(defaultInferenceReceivingPatternAllowSet); DEFAULT.setQueryForAllObjectProperties(false); @@ -107,36 +107,36 @@ public class ReasonerConfiguration { COMPLETE.setQueryForAllObjectProperties(true); COMPLETE.setReasonOnAllDatatypePropertyStatements(true); COMPLETE.setQueryForAllDatatypeProperties(true); - Set completeInferenceReceivingPatternAllowSet = new HashSet(); + Set completeInferenceReceivingPatternAllowSet = new HashSet<>(); completeInferenceReceivingPatternAllowSet.addAll(defaultInferenceReceivingPatternAllowSet); - completeInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null, OWL.sameAs, null)); + completeInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern(OWL.sameAs)); // getting NPEs inside Pellet with differentFrom on 2.0.0-rc7 - //completeInferenceReceivingPatternAllowSet.add(ObjectPropertyStatementPatternFactory.getPattern(null, OWL.differentFrom, null)); + //completeInferenceReceivingPatternAllowSet.add(ReasonerStatementPattern.objectPattern( OWL.differentFrom, null)); COMPLETE.setInferenceReceivingPatternAllowSet(completeInferenceReceivingPatternAllowSet); } - public Set getInferenceDrivingPatternAllowSet() { + public Set getInferenceDrivingPatternAllowSet() { return this.inferenceDrivingPatternAllowSet; } - public void setInferenceDrivingPatternAllowSet(Set patternSet) { + public void setInferenceDrivingPatternAllowSet(Set patternSet) { this.inferenceDrivingPatternAllowSet = patternSet; } - public Set getInferenceDrivingPatternDenySet() { + public Set getInferenceDrivingPatternDenySet() { return this.inferenceDrivingPatternDenySet; } - public void setInferenceDrivingPatternDenySet(Set patternSet) { + public void setInferenceDrivingPatternDenySet(Set patternSet) { this.inferenceDrivingPatternDenySet = patternSet; } - public Set getInferenceReceivingPatternAllowSet() { + public Set getInferenceReceivingPatternAllowSet() { return this.inferenceReceivingPatternAllowSet; } - public void setInferenceReceivingPatternAllowSet(Set patternSet) { + public void setInferenceReceivingPatternAllowSet(Set patternSet) { this.inferenceReceivingPatternAllowSet = patternSet; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerStatementPattern.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerStatementPattern.java new file mode 100644 index 000000000..e1c1c627e --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/ReasonerStatementPattern.java @@ -0,0 +1,94 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import java.util.List; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.shared.Lock; + +/** + * For now, this only models Object Properties. + * + * It should be easy to add Data Property patterns by making this abstract and + * creating two concrete subclasses. + */ +public class ReasonerStatementPattern { + public static final ReasonerStatementPattern ANY_OBJECT_PROPERTY = new ReasonerStatementPattern( + null, null, null); + + public static ReasonerStatementPattern objectPattern(Property predicate) { + return new ReasonerStatementPattern(null, predicate, null); + } + + public static ReasonerStatementPattern objectPattern(Statement stmt) { + if (!stmt.getObject().isResource()) { + throw new IllegalArgumentException( + "Object of stmt must be a resource."); + } + return new ReasonerStatementPattern(stmt.getSubject(), + stmt.getPredicate(), stmt.getObject().asResource()); + } + + /** + * Any or all of these may be null, which acts as a wild card. + */ + private final Resource subject; + private final Property predicate; + private final Resource object; + private final String toString; + + private ReasonerStatementPattern(Resource subject, Property predicate, + Resource object) { + this.subject = subject; + this.predicate = predicate; + this.object = object; + this.toString = buildToString(); + } + + public Property getPredicate() { + return predicate; + } + + /** + * All fields must match, either by being equal, or by being a wild card. + */ + public boolean matches(ReasonerStatementPattern that) { + boolean sMatch = this.subject == null || that.subject == null + || this.subject.equals(that.subject); + boolean pMatch = this.predicate == null || that.predicate == null + || this.predicate.equals(that.predicate); + boolean oMatch = this.object == null || that.object == null + || this.object.equals(that.object); + return sMatch && pMatch && oMatch; + } + + /** + * Get a list of statements from this model that match this pattern. + */ + public List matchStatementsFromModel(Model m) { + m.enterCriticalSection(Lock.READ); + try { + return m.listStatements(subject, predicate, object).toList(); + } finally { + m.leaveCriticalSection(); + } + } + + public String buildToString() { + return "ReasonerStatementPattern[subject=" + + (subject == null ? "*" : subject.toString()) + ", predicate=" + + (predicate == null ? "*" : predicate.toString()) + + ", object=" + (object == null ? "*" : object.toString()) + + "]"; + } + + @Override + public String toString() { + return toString; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxChanges.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxChanges.java new file mode 100644 index 000000000..efe79d917 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxChanges.java @@ -0,0 +1,83 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; + +/** + * Accumulate changes to the TBox as they arrive. Then make them available to + * the TBox reasoner. + */ +public class TBoxChanges { + private final List addedStatements = Collections + .synchronizedList(new ArrayList()); + + private final List removedStatements = Collections + .synchronizedList(new ArrayList()); + + private final List deletedDataPropertyUris = Collections + .synchronizedList(new ArrayList()); + + private final List deletedObjectPropertyUris = Collections + .synchronizedList(new ArrayList()); + + // ---------------------------------------------------------------------- + // These methods are called when populating the changeSet. They must be + // thread-safe. + // ---------------------------------------------------------------------- + + public void addStatement(Statement stmt) { + addedStatements.add(stmt); + } + + public void removeStatement(Statement stmt) { + removedStatements.remove(stmt); + } + + public void deleteDataProperty(Statement stmt) { + Resource subject = stmt.getSubject(); + if (subject.isURIResource()) { + deletedDataPropertyUris.add(subject.getURI()); + } + } + + public void deleteObjectProperty(Statement stmt) { + Resource subject = stmt.getSubject(); + if (subject.isURIResource()) { + deletedObjectPropertyUris.add(subject.getURI()); + } + } + + // ---------------------------------------------------------------------- + // These methods are called when processing the changeSet. By that time, it + // is owned and accessed by a single thread. + // ---------------------------------------------------------------------- + + public boolean isEmpty() { + return addedStatements.isEmpty() && removedStatements.isEmpty() + && deletedDataPropertyUris.isEmpty() + && deletedObjectPropertyUris.isEmpty(); + } + + public List getAddedStatements() { + return addedStatements; + } + + public List getRemovedStatements() { + return removedStatements; + } + + public List getDeletedDataPropertyUris() { + return deletedDataPropertyUris; + } + + public List getDeletedObjectPropertyUris() { + return deletedObjectPropertyUris; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasoner.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasoner.java new file mode 100644 index 000000000..5839193ca --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasoner.java @@ -0,0 +1,87 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import java.util.List; + +import com.hp.hpl.jena.ontology.DatatypeProperty; +import com.hp.hpl.jena.ontology.ObjectProperty; +import com.hp.hpl.jena.ontology.Restriction; +import com.hp.hpl.jena.rdf.model.Statement; + +/** + * The functionality of a TBox reasoner. + * + * The reasoner will maintain its own TBox model. It will receive updates to + * that model and perform reasoning on it. It will answer queries about the + * contents of the model, when reasoning is complete. + */ +public interface TBoxReasoner { + + /** + * Add the additions and remove the removals. + */ + void updateReasonerModel(TBoxChanges changes); + + /** + * Chew on it and create the inferences. Report status. + */ + Status performReasoning(); + + /** + * List all of the ObjectProperties from the reasoner model, after updating + * and reasoning. + */ + List listObjectProperties(); + + /** + * List all of the DatatypeProperties from the reasoner model, after + * updating and reasoning. + */ + List listDatatypeProperties(); + + /** + * List all of the restrictions in the reasoner model, after updating and + * reasoning. + */ + List listRestrictions(); + + /** + * List all of the statements that satisfy any of these patterns, after + * updating and reasoning. + */ + List filterResults(List patternList); + + public static class Status { + public static final Status SUCCESS = new Status(true, false, ""); + public static final Status ERROR = new Status(true, true, ""); + + public static final Status inconsistent(String explanation) { + return new Status(false, false, explanation); + } + + private final boolean consistent; + private final boolean inErrorState; + private final String explanation; + + private Status(boolean consistent, boolean inErrorState, + String explanation) { + this.consistent = consistent; + this.inErrorState = inErrorState; + this.explanation = explanation; + } + + public boolean isConsistent() { + return consistent; + } + + public boolean isInErrorState() { + return inErrorState; + } + + public String getExplanation() { + return explanation; + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasonerDriver.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasonerDriver.java new file mode 100644 index 000000000..edd3da3a9 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/TBoxReasonerDriver.java @@ -0,0 +1,17 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner; + +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerStatus; + + +/** + * What calls can the ConfiguredReasonerListener make to drive the TBox + * reasoner? + */ +public interface TBoxReasonerDriver { + + void runSynchronizer(TBoxChanges changeSet); + + TBoxReasonerStatus getStatus(); +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/BasicTBoxReasonerDriver.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/BasicTBoxReasonerDriver.java new file mode 100644 index 000000000..9ffe21cb5 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/BasicTBoxReasonerDriver.java @@ -0,0 +1,253 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner.impl; + +import static edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel.IDLE; +import static edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel.WORKING; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.event.EditEvent; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerStatus; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ConfiguredReasonerListener; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.InferenceModelUpdater; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.PatternListBuilder; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerConfiguration; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerStatementPattern; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxChanges; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasoner; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasoner.Status; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasonerDriver; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableOntModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockedOntModel; +import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread; + +/** + * The basic implementation of the TBoxReasonerDriver. It gets help from a + * listener, an executor, and a reasoner. + * + * Create a listener that listens for changes to the TBox, but filters them + * according to a ReasonerConfiguration object. The listener accumulates the + * changes it likes, until it detects an ending EditEvent. Then it passes the + * change set back to the driver. + * + * Each time a change set is received, a task is created and given to the + * executor to run. The executor is single-threaded, so the change sets are + * processed in sequence. + * + * Processing involves the following steps: + * + * 1. Telling the reasoner about the changes, so it can update its own internal + * ontology model. + * + * 2. Telling the reasoner to re-inference its model. A status is returned. + * + * 3. Asking the reasoner for the inferences from its model. As with the initial + * changes, these inferences are filtered according to the + * ReasonerConfiguration. + * + * 4. Synchronizing the applications TBox inferences model with the inferences + * obtained from the reasoner. + * + * ---------------------- + * + * Possible optimization: if change sets come in quickly enough that the third + * set is received while the first is still being processed, it would be + * reasonable to merge the second and third sets into one. + */ +public class BasicTBoxReasonerDriver implements TBoxReasonerDriver { + private static final Log log = LogFactory + .getLog(BasicTBoxReasonerDriver.class); + + private final LockableOntModel lockableAssertionsModel; + private final LockableModel lockableInferencesModel; + private final LockableOntModel lockableFullModel; + + private final ReasonerConfiguration reasonerConfiguration; + + private final ConfiguredReasonerListener listener; + + private final Set pendingChangeSets; + + private final ExecutorService executorService; + + private final TBoxReasoner reasoner; + + private TBoxReasoner.Status innerStatus; + + public BasicTBoxReasonerDriver(OntModel assertionsModel, + Model inferencesModel, OntModel fullModel, TBoxReasoner reasoner, + ReasonerConfiguration reasonerConfiguration) { + this.lockableAssertionsModel = new LockableOntModel(assertionsModel); + this.lockableInferencesModel = new LockableModel(inferencesModel); + this.lockableFullModel = new LockableOntModel(fullModel); + this.reasoner = reasoner; + this.reasonerConfiguration = reasonerConfiguration; + + this.listener = new ConfiguredReasonerListener(reasonerConfiguration, + this); + + this.pendingChangeSets = Collections + .synchronizedSet(new HashSet()); + + this.executorService = Executors.newFixedThreadPool(1, + new VitroBackgroundThread.Factory("TBoxReasoner")); + + assertionsModel.getBaseModel().register(listener); + fullModel.getBaseModel().register(listener); + + doInitialReasoning(); + } + + private void doInitialReasoning() { + try (LockedOntModel assertionsModel = lockableAssertionsModel.read()) { + for (ReasonerStatementPattern pat : reasonerConfiguration + .getInferenceDrivingPatternAllowSet()) { + listener.addedStatements(assertionsModel.listStatements( + (Resource) null, pat.getPredicate(), (RDFNode) null)); + } + } + listener.notifyEvent(null, new EditEvent(null, false)); + } + + @Override + public TBoxReasonerStatus getStatus() { + return new FullStatus(innerStatus, !pendingChangeSets.isEmpty()); + } + + @Override + public void runSynchronizer(TBoxChanges changeSet) { + if (!changeSet.isEmpty()) { + executorService.execute(new ReasoningTask(changeSet)); + } + } + + /** + * Shut down the thread that runs the reasoning tasks. Don't wait longer + * than 1 minute. + */ + public void shutdown() { + executorService.shutdown(); + int waited = 0; + while (waited < 60 && !executorService.isTerminated()) { + try { + log.info("Waiting for TBox reasoner to terminate."); + executorService.awaitTermination(5, TimeUnit.SECONDS); + waited += 5; + } catch (InterruptedException e) { + // Should never happen. + e.printStackTrace(); + break; + } + } + if (!executorService.isTerminated()) { + log.warn("Forcing TBox reasoner to terminate."); + executorService.shutdownNow(); + } + if (!executorService.isTerminated()) { + log.error("TBox reasoner did not terminate."); + } + } + + private class ReasoningTask implements Runnable { + private final TBoxChanges changes; + private List patternList; + + public ReasoningTask(TBoxChanges changes) { + this.changes = changes; + pendingChangeSets.add(changes); + } + + @Override + public void run() { + try { + setWorking(); + + reasoner.updateReasonerModel(changes); + innerStatus = reasoner.performReasoning(); + + buildPatternList(); + updateInferencesModel(); + + setIdle(); + } finally { + pendingChangeSets.remove(changes); + } + } + + private void setWorking() { + Thread current = Thread.currentThread(); + if (current instanceof VitroBackgroundThread) { + ((VitroBackgroundThread) current).setWorkLevel(WORKING); + } + } + + private void setIdle() { + Thread current = Thread.currentThread(); + if (current instanceof VitroBackgroundThread) { + ((VitroBackgroundThread) current).setWorkLevel(IDLE); + } + } + + private void buildPatternList() { + PatternListBuilder patternListBuilder = new PatternListBuilder( + reasonerConfiguration, reasoner, changes); + this.patternList = patternListBuilder.build(); + } + + private void updateInferencesModel() { + InferenceModelUpdater inferenceModelUpdater = new InferenceModelUpdater( + reasoner, lockableInferencesModel, lockableFullModel, + listener); + inferenceModelUpdater.update(patternList); + } + } + + private static class FullStatus implements TBoxReasonerStatus { + private final TBoxReasoner.Status reasonerStatus; + private final boolean reasoning; + + public FullStatus(Status reasonerStatus, boolean reasoning) { + this.reasonerStatus = reasonerStatus; + this.reasoning = reasoning; + } + + @Override + public boolean isReasoning() { + return reasoning; + } + + @Override + public boolean isConsistent() { + return reasonerStatus.isConsistent(); + } + + @Override + public boolean isInErrorState() { + return reasonerStatus.isInErrorState(); + } + + @Override + public String getExplanation() { + String explanation = reasonerStatus.getExplanation(); + return explanation == null ? "" : explanation; + } + + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasoner.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasoner.java new file mode 100644 index 000000000..72b2ed49f --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasoner.java @@ -0,0 +1,114 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner.impl.pellet; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mindswap.pellet.exceptions.InconsistentOntologyException; +import org.mindswap.pellet.jena.PelletInfGraph; + +import com.hp.hpl.jena.ontology.DatatypeProperty; +import com.hp.hpl.jena.ontology.ObjectProperty; +import com.hp.hpl.jena.ontology.Restriction; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.vocabulary.OWL; +import com.hp.hpl.jena.vocabulary.RDFS; + +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerConfiguration; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerStatementPattern; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxChanges; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.TBoxReasoner; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableOntModel; +import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockedOntModel; + +/** + * An implementation the TBoxReasonerWrapper for Pellet. + */ +public class PelletTBoxReasoner implements TBoxReasoner { + private static final Log log = LogFactory + .getLog(PelletTBoxReasoner.class); + + private final LockableOntModel lockablePelletModel; + + public PelletTBoxReasoner(ReasonerConfiguration reasonerConfiguration) { + this.lockablePelletModel = new LockableOntModel( + ModelFactory.createOntologyModel(reasonerConfiguration + .getOntModelSpec())); + } + + @Override + public void updateReasonerModel(TBoxChanges changes) { + try (LockedOntModel pelletModel = lockablePelletModel.write()) { + pelletModel.remove(changes.getRemovedStatements()); + pelletModel.add(changes.getAddedStatements()); + } + } + + @Override + public Status performReasoning() { + try (LockedOntModel pelletModel = lockablePelletModel.write()) { + try { + pelletModel.rebind(); + pelletModel.prepare(); + return Status.SUCCESS; + } catch (InconsistentOntologyException ioe) { + String explanation = ((PelletInfGraph) pelletModel.getGraph()) + .getKB().getExplanation(); + log.error(ioe); + log.error(explanation); + return Status.inconsistent(explanation); + } catch (Exception e) { + log.error("Exception during inference", e); + return Status.ERROR; + } + } + } + + @Override + public List listObjectProperties() { + try (LockedOntModel pelletModel = lockablePelletModel.read()) { + return pelletModel.listObjectProperties().toList(); + } + } + + @Override + public List listDatatypeProperties() { + try (LockedOntModel pelletModel = lockablePelletModel.read()) { + return pelletModel.listDatatypeProperties().toList(); + } + } + + @Override + public List filterResults( + List patternList) { + List filtered = new ArrayList<>(); + try (LockedOntModel pelletModel = lockablePelletModel.read()) { + for (ReasonerStatementPattern pattern : patternList) { + filtered.addAll(pattern.matchStatementsFromModel(pelletModel)); + } + } + for (Iterator fit = filtered.iterator(); fit.hasNext(); ) { + Statement stmt = fit.next(); + if (stmt.getObject().equals(RDFS.Resource)) { + fit.remove(); + } else if (stmt.getSubject().equals(OWL.Nothing)) { + fit.remove(); + } else if (stmt.getObject().equals(OWL.Nothing)) { + fit.remove(); + } + } + return filtered; + } + + @Override + public List listRestrictions() { + try (LockedOntModel pelletModel = lockablePelletModel.read()) { + return pelletModel.listRestrictions().toList(); + } + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasonerModule.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasonerModule.java new file mode 100644 index 000000000..e04c7c54d --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/tboxreasoner/impl/pellet/PelletTBoxReasonerModule.java @@ -0,0 +1,102 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.tboxreasoner.impl.pellet; + +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_INFERENCES; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_UNION; + +import java.util.List; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.ontology.Restriction; +import com.hp.hpl.jena.rdf.model.Model; + +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modules.Application; +import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerStatus; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.ReasonerConfiguration; +import edu.cornell.mannlib.vitro.webapp.tboxreasoner.impl.BasicTBoxReasonerDriver; + +/** + * Configure a Pellet reasoner on the TBox. + */ +public class PelletTBoxReasonerModule implements TBoxReasonerModule { + private static final Log log = LogFactory + .getLog(PelletTBoxReasonerModule.class); + + private PelletTBoxReasoner reasoner; + private BasicTBoxReasonerDriver driver; + + @Override + public void startup(Application application, ComponentStartupStatus ss) { + ServletContext ctx = application.getServletContext(); + + ContextModelAccess contextModels = ModelAccess.on(ctx); + OntModel tboxAssertionsModel = contextModels + .getOntModel(TBOX_ASSERTIONS); + Model tboxInferencesModel = contextModels.getOntModel(TBOX_INFERENCES) + .getBaseModel(); + OntModel tboxUnionModel = contextModels.getOntModel(TBOX_UNION); + + reasoner = new PelletTBoxReasoner(ReasonerConfiguration.DEFAULT); + driver = new BasicTBoxReasonerDriver(tboxAssertionsModel, + tboxInferencesModel, tboxUnionModel, reasoner, + ReasonerConfiguration.DEFAULT); + + ss.info("Pellet reasoner connected for the TBox"); + + waitForTBoxReasoning(); + } + + @Override + public TBoxReasonerStatus getStatus() { + if (driver == null) { + throw new IllegalStateException( + "PelletTBoxReasonerModule has not been started."); + } + return driver.getStatus(); + } + + @Override + public List listRestrictions() { + if (reasoner == null) { + throw new IllegalStateException( + "PelletTBoxReasonerModule has not been started."); + } + return reasoner.listRestrictions(); + } + + @Override + public void shutdown(Application application) { + driver.shutdown(); + } + + @Override + public void waitForTBoxReasoning() { + int sleeps = 0; + // sleep at least once to make sure the TBox reasoning gets started + while ((0 == sleeps) + || ((sleeps < 1000) && getStatus().isReasoning())) { + if (((sleeps - 1) % 10) == 0) { // print message at 10 second + // intervals + log.info("Waiting for initial TBox reasoning to complete"); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // This should never happen. + e.printStackTrace(); + } + sleeps++; + } + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java index 47780558f..437b8d48d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/threads/VitroBackgroundThread.java @@ -10,6 +10,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -106,4 +108,25 @@ public class VitroBackgroundThread extends Thread { return flags; } } + + /** + * A factory class, for use in Executors, that creates threads with + * successive names. + */ + public static class Factory implements ThreadFactory{ + private final String threadName; + private final AtomicInteger index; + + public Factory(String threadName) { + this.threadName = threadName; + this.index = new AtomicInteger(); + } + + @Override + public Thread newThread(Runnable r) { + return new VitroBackgroundThread(r, threadName + "_" + index.getAndIncrement()); + } + + } + } diff --git a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/modules/ApplicationStub.java b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/modules/ApplicationStub.java index f5aeaf92f..b8534649c 100644 --- a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/modules/ApplicationStub.java +++ b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/modules/ApplicationStub.java @@ -12,6 +12,7 @@ import edu.cornell.mannlib.vitro.webapp.modules.Application; import edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage; import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ConfigurationTripleSource; import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource; @@ -101,4 +102,11 @@ public class ApplicationStub implements Application { "ApplicationStub.getConfigurationTripleSource() not implemented."); } + @Override + public TBoxReasonerModule getTBoxReasonerModule() { + // TODO Auto-generated method stub + throw new RuntimeException("ApplicationStub.getTBoxReasonerModule() not implemented."); + + } + } diff --git a/webapp/web/WEB-INF/resources/startup_listeners.txt b/webapp/web/WEB-INF/resources/startup_listeners.txt index c5c9803eb..9c3fc7a42 100644 --- a/webapp/web/WEB-INF/resources/startup_listeners.txt +++ b/webapp/web/WEB-INF/resources/startup_listeners.txt @@ -34,6 +34,7 @@ edu.cornell.mannlib.vitro.webapp.servlet.setup.RemoveObsoletePermissions edu.cornell.mannlib.vitro.webapp.servlet.setup.FileGraphSetup +edu.cornell.mannlib.vitro.webapp.application.ApplicationImpl$ReasonersSetup edu.cornell.mannlib.vitro.webapp.servlet.setup.SimpleReasonerSetup #edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateKnowledgeBase diff --git a/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-ontologyEditor.ftl b/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-ontologyEditor.ftl index 946861559..20495f03d 100644 --- a/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-ontologyEditor.ftl +++ b/webapp/web/templates/freemarker/body/siteAdmin/siteAdmin-ontologyEditor.ftl @@ -6,11 +6,11 @@

${i18n().ontology_editor}

- <#if ontologyEditor.pellet?has_content> + <#if ontologyEditor.tboxReasonerStatus?has_content>
-

${ontologyEditor.pellet.error}

- <#if ontologyEditor.pellet.explanation?has_content> -

${i18n().cause} ${ontologyEditor.pellet.explanation}

+

${ontologyEditor.tboxReasonerStatus.error}

+ <#if ontologyEditor.tboxReasonerStatus.explanation?has_content> +

${i18n().cause} ${ontologyEditor.tboxReasonerStatus.explanation}