Skip to content

Commit

Permalink
Add option to trigger internal Jena consistency checks during loading (
Browse files Browse the repository at this point in the history
…#29)

There are cases where the loaded RDF graph is obviously against the OWL constraints that have been used to define a class: for instance, if someone says "Computers are things that have one motherboard" and then the graph has something said to be a Computer with two motherboards.

This commit adds a checkbox in the model configuration dialog which triggers Jena's internal consistency checks, and will raise an error during model loading if an inconsistency is found.

---------

Co-authored-by: Antonio Garcia-Dominguez <a.garcia-dominguez@york.ac.uk>
  • Loading branch information
OwenR-York and agarciadom authored Feb 13, 2025
1 parent 099cfcc commit 8635167
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,17 @@ protected String getModelType() {
return "RDF";
}

//
// Ordering of Groups in Dialogue window

@Override
protected void createGroups(Composite control) {
createNameAliasGroup(control);
createDataModelRDFUrlsGroup(control);
createSchemaModelRDFUrlsGroup(control);
createNamespaceMappingGroup(control);
createLanguagePreferenceGroup(control);
createValidateModelGroup(control);
}

private Composite createNamespaceMappingGroup(Composite parent) {
Expand Down Expand Up @@ -490,7 +494,6 @@ public void widgetSelected(SelectionEvent e) {

protected Label languagePreferenceLabel;
protected Text languagePreferenceText;

private Composite createLanguagePreferenceGroup(Composite parent) {
final Composite groupContent = DialogUtil.createGroupContainer(parent, "Language tag preference", 1);

Expand All @@ -511,7 +514,24 @@ public void modifyText(ModifyEvent event) {
return groupContent;
}

@Override

protected String validateModel;

protected Button validateModelCheckBox;
private Composite createValidateModelGroup(Composite parent) {
final Composite groupContent = DialogUtil.createGroupContainer(parent, "Model validation", 1);

validateModelCheckBox = new Button(groupContent, SWT.CHECK);
validateModelCheckBox.setText("Run Jena's model validation on loaded models");
boolean isJenaValidationEnabled = RDFModel.VALIDATION_SELECTION_JENA.equals(validateModel);
validateModelCheckBox.setSelection(isJenaValidationEnabled);

groupContent.layout();
groupContent.pack();
return groupContent;
}

@Override
protected void loadProperties(){
super.loadProperties();
if (properties == null) return;
Expand Down Expand Up @@ -544,7 +564,11 @@ protected void loadProperties(){
}

languagePreferenceText.setText(properties.getProperty(RDFModel.PROPERTY_LANGUAGE_PREFERENCE));


// Load any saved property and default to Jena if none
validateModel = properties.getProperty(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_JENA);
validateModelCheckBox.setSelection(RDFModel.VALIDATION_SELECTION_JENA.equals(validateModel));

this.dataModelUrlListViewer.refresh();
this.schemaModelUrlListViewer.refresh();
this.nsMappingTable.refresh();
Expand Down Expand Up @@ -572,6 +596,12 @@ protected void storeProperties(){

properties.put(RDFModel.PROPERTY_LANGUAGE_PREFERENCE,
languagePreferenceText.getText().replaceAll("\\s", ""));

if (validateModelCheckBox.getSelection()) {
properties.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_JENA);
} else {
properties.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_NONE);
}
}

protected void validateForm() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Map.Entry;

import org.apache.jena.ontology.OntModel;
import org.apache.jena.ontology.OntModelSpec;
import org.apache.jena.rdf.model.InfModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
Expand All @@ -31,6 +32,8 @@
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.reasoner.Reasoner;
import org.apache.jena.reasoner.ReasonerRegistry;
import org.apache.jena.reasoner.ValidityReport;
import org.apache.jena.reasoner.ValidityReport.Report;
import org.apache.jena.vocabulary.RDF;
import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
Expand All @@ -57,9 +60,15 @@ public class RDFModel extends CachedModel<RDFModelElement> {
* pairs will take precedence over existing pairs in the resource.
*/
public static final String PROPERTY_PREFIXES = "prefixes";
public static final String PROPERTY_VALIDATE_MODEL = "enableModelValidation";

public static final String VALIDATION_SELECTION_JENA = "jena";
public static final String VALIDATION_SELECTION_NONE = "none";
public static final String VALIDATION_SELECTION_DEFAULT = VALIDATION_SELECTION_JENA;

protected final List<String> languagePreference = new ArrayList<>();
protected final Map<String, String> customPrefixesMap = new HashMap<>();
protected String validationMode = VALIDATION_SELECTION_DEFAULT;

// TODO add to this list to cover reasoner types in the ReasonerRegistry Class
public enum ReasonerType {
Expand Down Expand Up @@ -169,6 +178,8 @@ public void load(StringProperties properties, IRelativePathResolver resolver) th
loadCommaSeparatedProperty(properties, PROPERTY_DATA_URIS, this.dataURIs);
loadCommaSeparatedProperty(properties, PROPERTY_SCHEMA_URIS, this.schemaURIs);

this.validationMode = properties.getProperty(RDFModel.PROPERTY_VALIDATE_MODEL, VALIDATION_SELECTION_JENA);

this.customPrefixesMap.clear();
String sPrefixes = properties.getProperty(PROPERTY_PREFIXES, "").strip();
if (!sPrefixes.isEmpty()) {
Expand Down Expand Up @@ -201,6 +212,15 @@ public void load(StringProperties properties, IRelativePathResolver resolver) th
}

load();

// After Loading all scheme, data models and inferring the full model, validate
if (VALIDATION_SELECTION_JENA.equals(validationMode)) {
try {
validateModel();
} catch (Exception e) {
throw new EolModelLoadingException(e, this);
}
}
}

protected void loadCommaSeparatedProperty(StringProperties properties, String propertyName, List<String> targetList) {
Expand Down Expand Up @@ -322,7 +342,7 @@ protected void loadModel() throws EolModelLoadingException {
}

//Create an OntModel to handle the data model being loaded or inferred from data and schema
this.model = ModelFactory.createOntologyModel();
this.model = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_RULE_INF);

if (reasonerType == ReasonerType.NONE) {
// Only the OntModel bits are added to the dataModel being loaded.
Expand All @@ -343,6 +363,42 @@ protected void loadModel() throws EolModelLoadingException {
}
}

protected void validateModel() throws Exception {
/*
* The way the model is validated by Jena depends on how the new OntModel was
* created by the ModelFactory.
*/

ValidityReport modelValidityReport = model.validate();
if (!modelValidityReport.isValid() || !modelValidityReport.isClean()) {
StringBuilder sb = new StringBuilder("The loaded model is not valid or not clean\n");
int i = 1;
for (Iterator<Report> o = modelValidityReport.getReports(); o.hasNext();) {
ValidityReport.Report report = (ValidityReport.Report) o.next();
sb.append(String.format(" %d: %s", i, report.toString()));
i++;
}
throw new Exception(sb.toString());
}
}

public String getValidationMode() {
return validationMode;
}

/**
* Changes the internal consistency validation mode used during loading.
*
* @param mode New mode. Must be one of {@code RDFModel#VALIDATION_SELECTION_NONE} or
* {@code RDFModel#VALIDATION_SELECTION_JENA}.
*/
public void setValidationMode(String mode) {
if (!VALIDATION_SELECTION_JENA.equals(mode) && !VALIDATION_SELECTION_NONE.equals(mode)) {
throw new IllegalArgumentException("Unknown validation mode " + mode);
}
this.validationMode = mode;
}

@Override
protected void disposeModel() {
model = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ protected StringProperties createStringProperties(String dataModelUri, String sc
props.put(RDFModel.PROPERTY_DATA_URIS, dataModelUri);
props.put(RDFModel.PROPERTY_SCHEMA_URIS, schemaModelUri);
props.put(RDFModel.PROPERTY_LANGUAGE_PREFERENCE, languagePreference);

/*
* Throws model validity errors for computer with 2 motherboards (expected to
* fail Jena validation).
*/
props.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_NONE);
return props;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@prefix : <urn:x-hp:eg/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .

:alienBox51 a :GamingComputer .

:bigName42 a :Computer ;
:bundle :binNameSpecialBundle ;
:motherBoard :bigNameSpecialMB .

:whiteBoxZX a :Computer ;
:bundle :actionPack ;
:motherBoard :nForce .

:actionPack a :GameBundle .

:bigNameSpecialMB owl:differentFrom :nForce2 .

:unknownMB :hasGraphics :gamingGraphics .

Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ public void getPersonInformation() throws Exception {
"foaf:Person",
model.getTypeNameOf(firstPerson));

assertEquals("The model should only report the Person type and the rdfs Resource",
new HashSet<>(Arrays.asList("foaf:Person", "rdfs:Resource")),
assertEquals("The model should only report the foaf Person, rdfs Resource and owl Thing",
new HashSet<>(Arrays.asList("foaf:Person", "rdfs:Resource", "owl:Thing")),
new HashSet<>(model.getAllTypeNamesOf(firstPerson)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ protected void loadModel(String dataModelUri, String schemaModelUri, String lang
props.put(RDFModel.PROPERTY_DATA_URIS, dataModelUri);
props.put(RDFModel.PROPERTY_SCHEMA_URIS, schemaModelUri);
props.put(RDFModel.PROPERTY_LANGUAGE_PREFERENCE, languagePreference);
props.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_NONE);
model.load(props);

this.context = new EolContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ protected void loadModel(String dataModelUri, String schemaModelUri, String lang
props.put(RDFModel.PROPERTY_DATA_URIS, dataModelUri);
props.put(RDFModel.PROPERTY_SCHEMA_URIS, schemaModelUri);
props.put(RDFModel.PROPERTY_LANGUAGE_PREFERENCE, languagePreference);

// There is a known issue in the model required for tests
props.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_NONE);
model.load(props);

this.context = new EolContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/********************************************************************************
* Copyright (c) 2024 University of York
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Antonio Garcia-Dominguez - initial API and implementation
********************************************************************************/
package org.eclipse.epsilon.emc.rdf;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.junit.Test;

public class RDFModelValidationTest {

private static final String OWL_DEMO_DATAMODEL_INVALID = "resources/OWL/owlDemoData.ttl";
private static final String OWL_DEMO_DATAMODEL_VALID = "resources/OWL/owlDemoData_valid.ttl";
private static final String OWL_DEMO_SCHEMAMODEL = "resources/OWL/owlDemoSchema.ttl";

private static final String LANGUAGE_PREFERENCE_EN_STRING = "en";

private RDFModel model;

@Test
public void loadInvalidModel() {

try {
loadModel(OWL_DEMO_DATAMODEL_INVALID);
fail("An exception was expected");
} catch (EolModelLoadingException e) {
String sErrors = e.getMessage();
assertTrue("The model loaded should FAIL validation (6 errors relating to BigName42 having 2 motherboards)",
sErrors.contains("not valid"));
}
}

@Test
public void loadValidModel() throws EolModelLoadingException {
loadModel(OWL_DEMO_DATAMODEL_VALID);
}

// Functions not tests

protected void loadModel(String dataModelUri) throws EolModelLoadingException {
this.model = new RDFModel();
StringProperties props = new StringProperties();
props.put(RDFModel.PROPERTY_DATA_URIS, dataModelUri);
props.put(RDFModel.PROPERTY_SCHEMA_URIS, OWL_DEMO_SCHEMAMODEL);
props.put(RDFModel.PROPERTY_LANGUAGE_PREFERENCE, LANGUAGE_PREFERENCE_EN_STRING);
props.put(RDFModel.PROPERTY_VALIDATE_MODEL, RDFModel.VALIDATION_SELECTION_JENA);
model.load(props);
}

}

0 comments on commit 8635167

Please sign in to comment.