Merge pull request #292 from brianjlowe/issue/VIVO-3713

VIVO-3713 Avoid excessive use of resources when ABox firsttime data are changed
This commit is contained in:
Dragan Ivanovic 2022-05-28 08:44:33 +02:00 committed by GitHub
commit fb4594ed3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 286 additions and 164 deletions

View file

@ -98,7 +98,7 @@ public class ConfigurationModelsSetup implements ServletContextListener {
OntModel baseModelFirsttime = VitroModelFactory.createOntologyModel();
RDFFilesLoader.loadFirstTimeFiles(ctx, modelPath, baseModelFirsttime, true);
if (baseModelFirsttime.isIsomorphicWith(baseModelFirsttimeBackup)) {
if (RDFFilesLoader.areIsomporphic(baseModelFirsttime, baseModelFirsttimeBackup)) {
log.debug("They are the same, so do nothing: '" + modelPath + "'");
} else {
log.debug("They differ:" + modelPath + ", compare values in configuration models with user's triplestore");
@ -132,9 +132,8 @@ public class ConfigurationModelsSetup implements ServletContextListener {
// remove special cases for display, problem with blank nodes
if (modelIdString.equals("display")) {
removeBlankTriples(difOldNew);
removeBlankTriples(difNewOld);
RDFFilesLoader.removeBlankTriples(difOldNew);
RDFFilesLoader.removeBlankTriples(difNewOld);
}
if (difOldNew.isEmpty() && difNewOld.isEmpty()) {
@ -149,7 +148,7 @@ public class ConfigurationModelsSetup implements ServletContextListener {
log.debug("Difference for " + modelIdString + " (old -> new), these triples should be removed: " + out);
// Check if the UI-changes Overlap with the changes made in the fristtime-files
checkUiChangesOverlapWithFileChanges(baseModel, userModel, difOldNew);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(baseModel, userModel, difOldNew);
// before we remove the triples, we need to compare values in back up firsttime with user's triplestore
// if the triples which should be removed are still in user´s triplestore, remove them
@ -167,7 +166,7 @@ public class ConfigurationModelsSetup implements ServletContextListener {
log.debug("Difference for " + modelIdString + " (new -> old), these triples should be added: " + out2);
// Check if the UI-changes Overlap with the changes made in the fristtime-files
checkUiChangesOverlapWithFileChanges(baseModel, userModel, difNewOld);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(baseModel, userModel, difNewOld);
// before we add the triples, we need to compare values in back up firsttime with user's triplestore
// if the triples which should be added are not already in user´s triplestore, add them
@ -186,85 +185,6 @@ public class ConfigurationModelsSetup implements ServletContextListener {
return updatedFiles;
}
/**
* Check if the UI-changes Overlap with the changes made in the fristtime-files, if they overlap these changes are not applied to the user-model (UI)
*
* @param baseModel firsttime backup model
* @param userModel current state in the system (user/UI-model)
* @param changesModel the changes between firsttime-files and firttime-backup
*/
private void checkUiChangesOverlapWithFileChanges(Model baseModel, Model userModel, Model changesModel) {
log.debug("Beginn check if subtractions from Backup-firsttime model to current state of firsttime-files were changed in user-model (via UI)");
Model changesUserModel = userModel.difference(baseModel);
List<Statement> changedInUIandFileStatements = new ArrayList<Statement>();
if(!changesUserModel.isEmpty())
{
removeBlankTriples(changesUserModel);
StringWriter out3 = new StringWriter();
changesUserModel.write(out3, "TTL");
log.debug("There were changes in the user-model via UI which have also changed in the firsttime files, the following triples will not be updated");
// iterate all statements and check if the ones which should be removed were not changed via the UI
StmtIterator iter = changesUserModel.listStatements();
while (iter.hasNext()) {
Statement stmt = iter.nextStatement(); // get next statement
Resource subject = stmt.getSubject(); // get the subject
Property predicate = stmt.getPredicate(); // get the predicate
RDFNode object = stmt.getObject(); // get the object
StmtIterator iter2 = changesModel.listStatements();
while (iter2.hasNext()) {
Statement stmt2 = iter2.nextStatement(); // get next statement
Resource subject2 = stmt2.getSubject(); // get the subject
Property predicate2 = stmt2.getPredicate(); // get the predicate
RDFNode object2 = stmt2.getObject(); // get the object
// if subject and predicate are equal but the object differs and the language tag is the same, do not update these triples
// this case indicates an change in the UI, which should not be overwriten from the firsttime files
if(subject.equals(subject2) && predicate.equals(predicate2) && !object.equals(object2) ) {
// if object is an literal, check the language tag
if (object.isLiteral() && object2.isLiteral()) {
// if the langauge tag is the same, remove this triple from the update list
if(object.asLiteral().getLanguage().equals(object2.asLiteral().getLanguage())) {
log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
} else {
log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
}
}
}
// remove triples which were changed in the user model (UI) from the list
changesModel.remove(changedInUIandFileStatements);
} else {
log.debug("There were no changes in the user-model via UI compared to the backup-firsttime-model");
}
}
/**
* Remove all triples where subject or object is blank (Anon)
*/
private void removeBlankTriples(Model model) {
StmtIterator iter = model.listStatements();
List<Statement> removeStatement = new ArrayList<Statement>();
while (iter.hasNext()) {
Statement stmt = iter.nextStatement(); // get next statement
Resource subject = stmt.getSubject(); // get the subject
RDFNode object = stmt.getObject(); // get the object
if(subject.isAnon() || object.isAnon())
{
removeStatement.add(stmt);
}
}
model.remove(removeStatement);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Nothing to tear down.

View file

@ -3,22 +3,21 @@
package edu.cornell.mannlib.vitro.webapp.servlet.setup;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS_FIRSTTIME_BACKUP;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS_FIRSTTIME_BACKUP;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA_FIRSTTIME_BACKUP;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS_FIRSTTIME_BACKUP;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.io.StringWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jena.ontology.OntModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Property;
@ -28,9 +27,6 @@ import org.apache.jena.shared.Lock;
import org.apache.jena.util.ResourceUtils;
import org.apache.jena.util.iterator.ClosableIterator;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.rdf.model.RDFNode;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess;
@ -129,23 +125,24 @@ public class ContentModelSetup extends JenaDataSourceSetupBase
* URI of the Portal based on the default namespace.
*/
private void setPortalUriOnFirstTime(Model model, ServletContext ctx) {
// Only a single portal is permitted in the initialization data
Resource portalResource = null;
// Only a single portal is permitted in the initialization data.
// Treat all blank nodes with type Portal as describing the same
// portal, and give them the same URI.
List<Resource> toRename = new ArrayList<Resource>();
ClosableIterator<Resource> portalResIt = model
.listSubjectsWithProperty(RDF.type,
model.getResource(VitroVocabulary.PORTAL));
try {
if (portalResIt.hasNext()) {
while (portalResIt.hasNext()) {
Resource portalRes = portalResIt.next();
if (portalRes.isAnon()) {
portalResource = portalRes;
toRename.add(portalRes);
}
}
} finally {
portalResIt.close();
}
if (portalResource != null) {
for (Resource portalResource : toRename) {
ResourceUtils.renameResource(portalResource, getDefaultNamespace(ctx) + "portal1");
}
}
@ -249,7 +246,7 @@ public class ContentModelSetup extends JenaDataSourceSetupBase
setPortalUriOnFirstTime(firsttimeFilesModel, ctx);
}
if ( firsttimeBackupModel.isIsomorphicWith(firsttimeFilesModel) ) {
if ( RDFFilesLoader.areIsomporphic(firsttimeBackupModel, firsttimeFilesModel) ) {
log.debug("They are the same, so do nothing: '" + modelPath + "'");
} else {
log.debug("They differ: '" + modelPath + "', compare values in configuration models with user's triplestore");
@ -301,7 +298,7 @@ public class ContentModelSetup extends JenaDataSourceSetupBase
log.debug("Difference for " + modelIdString + " (old -> new), these triples should be removed: " + out);
// Check if the UI-changes Overlap with the changes made in the fristtime-files
checkUiChangesOverlapWithFileChanges(baseModel, userModel, difOldNew);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(baseModel, userModel, difOldNew);
// before we remove the triples, we need to compare values in back up firsttime with user's triplestore
// if the triples which should be removed are still in user´s triplestore, remove them
@ -320,7 +317,7 @@ public class ContentModelSetup extends JenaDataSourceSetupBase
log.debug("Difference for " + modelIdString + " (new -> old), these triples should be added: " + out2);
// Check if the UI-changes Overlap with the changes made in the fristtime-files
checkUiChangesOverlapWithFileChanges(baseModel, userModel, difNewOld);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(baseModel, userModel, difNewOld);
// before we add the triples, we need to compare values in back up firsttime with user's triplestore
// if the triples which should be added are not already in user´s triplestore, add them
@ -339,67 +336,6 @@ public class ContentModelSetup extends JenaDataSourceSetupBase
return updatedFiles;
}
/**
* Check if the UI-changes Overlap with the changes made in the fristtime-files, if they overlap these changes are not applied to the user-model (UI)
*
* @param baseModel firsttime backup model
* @param userModel current state in the system (user/UI-model)
* @param changesModel the changes between firsttime-files and firttime-backup
*/
private void checkUiChangesOverlapWithFileChanges(Model baseModel, Model userModel, Model changesModel) {
log.debug("Beginn check if subtractions from Backup-firsttime model to current state of firsttime-files were changed in user-model (via UI)");
Model changesUserModel = userModel.difference(baseModel);
List<Statement> changedInUIandFileStatements = new ArrayList<Statement>();
if(!changesUserModel.isEmpty())
{
StringWriter out3 = new StringWriter();
changesUserModel.write(out3, "TTL");
log.debug("There were changes in the user-model via UI which have also changed in the firsttime files, the following triples will not be updated");
// iterate all statements and check if the ones which should be removed were not changed via the UI
StmtIterator iter = changesUserModel.listStatements();
while (iter.hasNext()) {
Statement stmt = iter.nextStatement(); // get next statement
Resource subject = stmt.getSubject(); // get the subject
Property predicate = stmt.getPredicate(); // get the predicate
RDFNode object = stmt.getObject(); // get the object
StmtIterator iter2 = changesModel.listStatements();
while (iter2.hasNext()) {
Statement stmt2 = iter2.nextStatement(); // get next statement
Resource subject2 = stmt2.getSubject(); // get the subject
Property predicate2 = stmt2.getPredicate(); // get the predicate
RDFNode object2 = stmt2.getObject(); // get the object
// if subject and predicate are equal but the object differs and the language tag is the same, do not update these triples
// this case indicates an change in the UI, which should not be overwriten from the firsttime files
if(subject.equals(subject2) && predicate.equals(predicate2) && !object.equals(object2) ) {
// if object is an literal, check the language tag
if (object.isLiteral() && object2.isLiteral()) {
// if the langauge tag is the same, remove this triple from the update list
if(object.asLiteral().getLanguage().equals(object2.asLiteral().getLanguage())) {
log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
} else {
log.debug("This two triples changed UI and files: \n UI: " + stmt + " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
}
}
}
// remove triples which were changed in the user model (UI) from the list
changesModel.remove(changedInUIandFileStatements);
} else {
log.debug("There were no changes in the user-model via UI compared to the backup-firsttime-model");
}
}
/* ===================================================================== */
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Nothing to do.

View file

@ -5,10 +5,12 @@ package edu.cornell.mannlib.vitro.webapp.servlet.setup;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@ -23,6 +25,11 @@ import org.apache.jena.ontology.OntModel;
import org.apache.jena.ontology.OntModelSpec;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
@ -207,5 +214,130 @@ public class RDFFilesLoader {
private RDFFilesLoader() {
// Nothing to initialize.
}
/**
* Check if the user model (UI) changes conflict with the changes made to
* the firsttime. If there is conflict, the user model UI value will be
* left unchanged.
*
* @param baseModel firsttime backup model
* @param userModel current state in the system (user/UI-model)
* @param changesModel the changes between firsttime-files and firsttime-backup
*/
public static void removeChangesThatConflictWithUIEdits(Model baseModel,
Model userModel, Model changesModel) {
log.debug("Check if subtractions from backup-firsttime model to"
+ " current state of firsttime-files were changed in user-model"
+ " (via UI)");
// We don't want to diff against the entire user model, which may be
// huge. We only care about subject/predicate pairs that exist in the
// changesModel. So extract these first from userModel into a
// scopedUserModel that we can use for diffing.
Model scopedUserModel = ModelFactory.createDefaultModel();
StmtIterator scopeIt = changesModel.listStatements();
while(scopeIt.hasNext()) {
Statement scopingStmt = scopeIt.next();
scopedUserModel.add(userModel.listStatements(
scopingStmt.getSubject(), scopingStmt.getPredicate(), (RDFNode) null));
}
log.debug("Scoped user model has " + scopedUserModel.size());
Model changesUserModel = scopedUserModel.difference(baseModel);
log.debug("Diff of scoped user model against firsttime backup has "
+ changesUserModel.size() + " triples");
List<Statement> changedInUIandFileStatements = new ArrayList<Statement>();
if(changesUserModel.isEmpty()) {
log.debug("There were no changes in the user-model via UI"
+ " compared to the backup-firsttime-model");
return;
}
removeBlankTriples(changesUserModel);
if(log.isDebugEnabled()) {
StringWriter out3 = new StringWriter();
changesUserModel.write(out3, "TTL");
log.debug("changesUserModel:\n" + out3);
}
log.debug("There were changes in the user-model via UI which have"
+ " also changed in the firsttime files. The following"
+ " triples will not be updated.");
// Iterate over all statements and check if the ones which should be
// removed were not changed via the UI
StmtIterator userChanges = changesUserModel.listStatements();
while (userChanges.hasNext()) {
Statement stmt = userChanges.nextStatement();
Resource subject = stmt.getSubject();
Property predicate = stmt.getPredicate();
RDFNode object = stmt.getObject();
StmtIterator firsttimeChanges = changesModel.listStatements(
subject, predicate, (RDFNode) null);
while (firsttimeChanges.hasNext()) {
Statement stmt2 = firsttimeChanges.nextStatement();
RDFNode object2 = stmt2.getObject();
// If subject and predicate are equal but the object differs
// and the language tag is the same, do not update these triples.
// This case indicates an change in the UI, which should not
// be overwritten from the firsttime files.
if(!object.equals(object2) ) {
// if object is an literal, check the language tag
if (object.isLiteral() && object2.isLiteral()) {
// if the language tag is the same, remove this
// triple from the update list
if(object.asLiteral().getLanguage().equals(
object2.asLiteral().getLanguage())) {
log.debug("This two triples changed UI and"
+ " files: \n UI: " + stmt
+ " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
} else {
log.debug("This two triples changed UI and"
+ " files: \n UI: " + stmt
+ " \n file: " +stmt2);
changedInUIandFileStatements.add(stmt2);
}
}
}
}
// remove triples which were changed in the user model (UI) from the list
changesModel.remove(changedInUIandFileStatements);
}
}
/**
* Remove all triples where subject or object is blank (Anon)
*/
public static void removeBlankTriples(Model model) {
List<Statement> removeStatement = new ArrayList<Statement>();
StmtIterator stmts = model.listStatements();
while (stmts.hasNext()) {
Statement stmt = stmts.nextStatement();
if(stmt.getSubject().isAnon() || stmt.getObject().isAnon())
{
removeStatement.add(stmt);
}
}
model.remove(removeStatement);
}
/**
* Check 'isomorphism' for purposes of propagating firsttime changes.
* Run Jena's isomorphism check, but if it fails only due to blank nodes,
* ignore and treat as isomorphic anyway. (Auto-updating firsttime
* changes should occur only with named nodes.)
* @param m1
* @param m2
* @return true if models are isomorphic or any lack of isomorphism exists
* only in blank nodes
*/
public static boolean areIsomporphic(Model m1, Model m2) {
boolean isIsomorphic = m1.isIsomorphicWith(m2);
if(isIsomorphic) {
return true;
} else {
Model diff1 = m1.difference(m2);
Model diff2 = m2.difference(m1);
removeBlankTriples(diff1);
removeBlankTriples(diff2);
return (diff1.isEmpty() && diff2.isEmpty());
}
}
}

View file

@ -0,0 +1,134 @@
package edu.cornell.mannlib.vitro.webapp.servlet.setup;
import static org.junit.Assert.assertTrue;
import java.io.StringReader;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.vocabulary.RDFS;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
public class RDFFilesLoaderTest extends AbstractTestClass {
@org.junit.Test
public void testFirsttimeUpdate() {
// the current state of the firsttime file on the filesystem
String fileModelRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish 'n' chips\"@en-GB . \n"
+ ":n1 rdfs:label \"fish and fries\"@en-US . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"Subway!\"@en-US . \n"
+ ":n2 rdfs:label \"metrou\"@ro-RO . \n";
// the backup of the previous state of the firsttime file
String backupModelRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish 'n' chips\"@en-GB . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"nooo\"@no-NO . \n";
// the current state of the user-editable model
String userModelRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish and chips\"@en-GB . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"subway\"@en-US . \n"
+ ":n2 rdfs:label \"nooo\"@no-NO . \n";
// the expected state of the user-editable model after firsttime
// updates have been applied
String userModelExpectedRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish and chips\"@en-GB . \n"
+ ":n1 rdfs:label \"fish and fries\"@en-US . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"subway\"@en-US . \n"
+ ":n2 rdfs:label \"metrou\"@ro-RO . \n";
Model fileModel = ModelFactory.createDefaultModel();
fileModel.read(new StringReader(fileModelRdf), null, "N3");
Model backupModel = ModelFactory.createDefaultModel();
backupModel.read(new StringReader(backupModelRdf), null, "N3");
Model userModel = ModelFactory.createDefaultModel();
userModel.read(new StringReader(userModelRdf), null, "N3");
Model userModelExpected = ModelFactory.createDefaultModel();
userModelExpected.read(new StringReader(userModelExpectedRdf), null, "N3");
Model additionsModel = fileModel.difference(backupModel);
Model retractionsModel = backupModel.difference(fileModel);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(backupModel, userModel, additionsModel);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(backupModel, userModel, retractionsModel);
userModel.remove(retractionsModel);
userModel.add(additionsModel);
// For any given triple (S, P, O) changed in the fileModel, it
// should only be propagated to the user model if the user model
// doesn't already have a conflicting triple (S, P, X) where O and X
// have the same language tag.
assertTrue("expected: " + userModelExpected + " but was: " + userModel,
userModelExpected.isIsomorphicWith(userModel));
}
@org.junit.Test
public void testFirsttimeUpdateEmptyBackup() {
// the current state of the firsttime file on the filesystem
String fileModelRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish 'n' chips\"@en-GB . \n"
+ ":n1 rdfs:label \"fish and fries\"@en-US . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"Subway!\"@en-US . \n"
+ ":n2 rdfs:label \"metrou\"@ro-RO . \n";
// the current state of the user-editable model
String userModelRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish and chips\"@en-GB . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"subway\"@en-US . \n"
+ ":n2 rdfs:label \"nooo\"@no-NO . \n";
// the expected state of the user-editable model after firsttime
// updates have been applied
String userModelExpectedRdf = "@prefix rdfs: <" + RDFS.getURI() + "> .\n"
+ "@prefix : <http://example.com/individual/> .\n"
+ ":n1 rdfs:label \"fish and chips\"@en-GB . \n"
+ ":n1 rdfs:label \"fish and fries\"@en-US . \n"
+ ":n2 rdfs:label \"tube\"@en-GB . \n"
+ ":n2 rdfs:label \"subway\"@en-US . \n"
+ ":n2 rdfs:label \"metrou\"@ro-RO . \n"
+ ":n2 rdfs:label \"nooo\"@no-NO . \n";
Model fileModel = ModelFactory.createDefaultModel();
fileModel.read(new StringReader(fileModelRdf), null, "N3");
Model backupModel = ModelFactory.createDefaultModel();
Model userModel = ModelFactory.createDefaultModel();
userModel.read(new StringReader(userModelRdf), null, "N3");
Model userModelExpected = ModelFactory.createDefaultModel();
userModelExpected.read(new StringReader(userModelExpectedRdf), null, "N3");
Model additionsModel = fileModel.difference(backupModel);
Model retractionsModel = backupModel.difference(fileModel);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(backupModel, userModel, additionsModel);
RDFFilesLoader.removeChangesThatConflictWithUIEdits(backupModel, userModel, retractionsModel);
userModel.remove(retractionsModel);
userModel.add(additionsModel);
// For any given triple (S, P, O) found in the fileModel, it
// should only be propagated to the user model if the user model
// doesn't already have a conflicting triple (S, P, X) where O and X
// have the same language tag.
assertTrue("expected: " + userModelExpected + " but was: " + userModel,
userModelExpected.isIsomorphicWith(userModel));
}
}