diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMaker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMaker.java index 8503dd2de..2e4d50c81 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMaker.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMaker.java @@ -18,7 +18,11 @@ import com.hp.hpl.jena.util.iterator.WrappedIterator; * that remove a model must remove its name from the list. * * This is a useful decorator for some ModelMakers where listModels() is a - * costly operation. + * costly operation. The drawback is that it will not see models that were + * created at a lower level; perhaps by RDFServiceDataset.getNamedModel(). + * + * If listModels() is not costly, you might be better off with a + * ModelMakerWithPersistentEmptyModels. */ public class ListCachingModelMaker extends AbstractModelMakerDecorator { private final Set modelNames; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModels.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModels.java new file mode 100644 index 000000000..22a1ee0e6 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModels.java @@ -0,0 +1,115 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess.adapters; + +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelMaker; +import com.hp.hpl.jena.rdf.model.ModelReader; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; +import com.hp.hpl.jena.util.iterator.WrappedIterator; + +/** + * This ModelMaker will remember the URIs of models you create, even if you + * don't add triples to them. Of course, when VIVO is shut down, this ModelMaker + * forgets. + * + * This is a useful decorator for some triple stores which do not remember empty + * models. SDB, for example. With this decorator, a GUI can offer to let you + * create a model, and then offer to let you add triples to it. Without this + * decorator, you would need to do that in a single step. + * + * If you are dealing with a triple store where listModels() is a costly + * operation, you might be better off using a ListCachingModelMaker instead. The + * drawback is that ListCachingModelMaker will not see models that were created + * at a lower level; perhaps by RDFServiceDataset.getNamedModel(). + * + * The methods that create a model must add its name to the list. The methods + * that remove a model must remove its name from the list. + */ +public class ModelMakerWithPersistentEmptyModels extends + AbstractModelMakerDecorator { + private final Set modelNames = new HashSet<>(); + + public ModelMakerWithPersistentEmptyModels(ModelMaker inner) { + super(inner); + } + + @Override + public boolean hasModel(String name) { + return modelNames.contains(name) || super.hasModel(name); + } + + @Override + public ExtendedIterator listModels() { + Set allNames = new TreeSet<>(modelNames); + allNames.addAll(super.listModels().toList()); + return WrappedIterator.create(allNames.iterator()); + } + + @Override + public Model getModel(String URL) { + Model m = super.getModel(URL); + if (m != null) { + addModelNameIfNeeded(URL); + } + return m; + } + + @Override + public Model getModel(String URL, ModelReader loadIfAbsent) { + Model m = super.getModel(URL, loadIfAbsent); + addModelNameIfNeeded(URL); + return m; + } + + @Override + public Model createModel(String name, boolean strict) { + Model m = super.createModel(name, strict); + addModelNameIfNeeded(name); + return m; + } + + @Override + public Model createModel(String name) { + return createModel(name, false); + } + + @Override + public Model openModel(String name) { + Model m = super.openModel(name); + addModelNameIfNeeded(name); + return m; + } + + @Override + public Model openModelIfPresent(String name) { + Model m = super.openModelIfPresent(name); + if (m != null) { + addModelNameIfNeeded(name); + } + return m; + } + + @Override + public Model openModel(String name, boolean strict) { + Model m = super.openModel(name, strict); + addModelNameIfNeeded(name); + return m; + } + + @Override + public void removeModel(String name) { + super.removeModel(name); + modelNames.remove(name); + } + + private void addModelNameIfNeeded(String name) { + if (!super.listModels().toList().contains(name)) { + modelNames.add(name); + } + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sdb/ContentTripleSourceSDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sdb/ContentTripleSourceSDB.java index eec10e046..69a2f37b5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sdb/ContentTripleSourceSDB.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sdb/ContentTripleSourceSDB.java @@ -32,8 +32,8 @@ import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; -import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ModelMakerWithPersistentEmptyModels; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.MaskingOntModelCache; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.ModelMakerOntModelCache; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.OntModelCache; @@ -164,7 +164,7 @@ public class ContentTripleSourceSDB extends ContentTripleSource { } private ModelMaker createModelMaker() { - return addContentDecorators(new ListCachingModelMaker( + return addContentDecorators(new ModelMakerWithPersistentEmptyModels( new MemoryMappingModelMaker(new RDFServiceModelMaker( this.rdfService), SMALL_CONTENT_MODELS))); } @@ -201,7 +201,7 @@ public class ContentTripleSourceSDB extends ContentTripleSource { public OntModelCache getShortTermOntModels(RDFService shortTermRdfService, OntModelCache longTermOntModelCache) { ModelMakerOntModelCache shortCache = new ModelMakerOntModelCache( - addContentDecorators(new ListCachingModelMaker( + addContentDecorators(new ModelMakerWithPersistentEmptyModels( new RDFServiceModelMaker(shortTermRdfService)))); MaskingOntModelCache combinedCache = new MaskingOntModelCache( diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java index 1e25b12f2..59c6a1e62 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java @@ -7,8 +7,8 @@ import com.hp.hpl.jena.rdf.model.ModelMaker; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; -import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ModelMakerWithPersistentEmptyModels; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.OntModelCache; import edu.cornell.mannlib.vitro.webapp.modules.Application; import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus; @@ -102,7 +102,7 @@ public class ContentTripleSourceSPARQL extends ContentTripleSource { } private ModelMaker createModelMaker() { - return addContentDecorators(new ListCachingModelMaker( + return addContentDecorators(new ModelMakerWithPersistentEmptyModels( new MemoryMappingModelMaker(new RDFServiceModelMaker( getRDFService()), SMALL_CONTENT_MODELS))); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ConfigurationTripleSourceTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ConfigurationTripleSourceTDB.java index 25085c43d..a497c9af5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ConfigurationTripleSourceTDB.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ConfigurationTripleSourceTDB.java @@ -12,8 +12,8 @@ import com.hp.hpl.jena.tdb.TDB; import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; -import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ModelMakerWithPersistentEmptyModels; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.OntModelCache; import edu.cornell.mannlib.vitro.webapp.modules.Application; import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus; @@ -78,7 +78,7 @@ public class ConfigurationTripleSourceTDB extends ConfigurationTripleSource { } private ModelMaker createModelMaker() { - ModelMaker longTermModelMaker = new ListCachingModelMaker( + ModelMaker longTermModelMaker = new ModelMakerWithPersistentEmptyModels( new MemoryMappingModelMaker(new RDFServiceModelMaker( this.unclosableRdfService), CONFIGURATION_MODELS)); return addConfigurationDecorators(longTermModelMaker); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java index e601192bb..5ac40e591 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java @@ -11,8 +11,8 @@ import com.hp.hpl.jena.tdb.TDB; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; -import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ModelMakerWithPersistentEmptyModels; import edu.cornell.mannlib.vitro.webapp.modelaccess.ontmodels.OntModelCache; import edu.cornell.mannlib.vitro.webapp.modules.Application; import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus; @@ -100,7 +100,7 @@ public class ContentTripleSourceTDB extends ContentTripleSource { } private ModelMaker createModelMaker() { - return addContentDecorators(new ListCachingModelMaker( + return addContentDecorators(new ModelMakerWithPersistentEmptyModels( new MemoryMappingModelMaker(new RDFServiceModelMaker( this.unclosableRdfService), SMALL_CONTENT_MODELS))); } diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModelsTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModelsTest.java new file mode 100644 index 000000000..4d4c530d6 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ModelMakerWithPersistentEmptyModelsTest.java @@ -0,0 +1,223 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess.adapters; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; + +import stubs.com.hp.hpl.jena.rdf.model.ModelMaker.ModelMakerStub; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.ModelMaker; +import com.hp.hpl.jena.rdf.model.ModelReader; +import com.hp.hpl.jena.shared.AlreadyExistsException; +import com.hp.hpl.jena.shared.CannotCreateException; +import com.hp.hpl.jena.shared.DoesNotExistException; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; + +/** + * Test the functions of a ModelMakerWithPersistentEmptyModels. Does it properly register the + * presence of a model with no triples? + */ +public class ModelMakerWithPersistentEmptyModelsTest extends AbstractTestClass { + private static final String URI_ONE = "http://model.one"; + private static final String URI_TWO = "http://model.two"; + private static final String URI_NONE = "http://model.does.not.exist"; + + private static final Model MODEL_ONE = createModel(); + private static final Model MODEL_TWO = createModel(); + private static final Model MODEL_DEFAULT = createModel(); + private static final Model MODEL_FRESH = createModel(); + + private ModelMaker rigorous; + private ModelMaker relaxed; + private ModelMaker mm; + private ModelReader modelReader; + + private static Model createModel() { + return ModelFactory.createDefaultModel(); + } + + @Before + public void setup() { + rigorous = ModelMakerStub.rigorous(MODEL_DEFAULT, MODEL_FRESH) + .put(URI_ONE, MODEL_ONE).put(URI_TWO, MODEL_TWO); + relaxed = ModelMakerStub.relaxed(MODEL_DEFAULT, MODEL_FRESH) + .put(URI_ONE, MODEL_ONE).put(URI_TWO, MODEL_TWO); + relaxed(); // call rigorous() to override, if desired. + } + + // ---------------------------------------------------------------------- + // The tests + // ---------------------------------------------------------------------- + + @SuppressWarnings("unused") + @Test(expected = NullPointerException.class) + public void nullInnerModel() { + new ModelMakerWithPersistentEmptyModels(null); + } + + @Test + public void listModels() { + assertList(URI_ONE, URI_TWO); + } + + @Test + public void hasModelExist() { + assertTrue(mm.hasModel(URI_ONE)); + } + + @Test + public void hasModelNonExist() { + assertFalse(mm.hasModel(URI_NONE)); + } + + @Test + public void createModelExist() { + assertEquals(MODEL_ONE, mm.createModel(URI_ONE)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void createModelNonExist() { + assertEquals(MODEL_FRESH, mm.createModel(URI_NONE)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + @Test(expected = AlreadyExistsException.class) + public void createModelStrictExist() { + mm.createModel(URI_ONE, true); + } + + @Test + public void createModelStrictNonExist() { + assertEquals(MODEL_FRESH, mm.createModel(URI_NONE, true)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + @Test + public void openModelExist() { + assertEquals(MODEL_TWO, mm.openModel(URI_TWO)); + assertList(URI_ONE, URI_TWO); + } + + @Test(expected = DoesNotExistException.class) + public void openModelRigorousNonExist() { + rigorous(); + mm.openModel(URI_NONE); + } + + @Test + public void openModelRelaxedNonExist() { + assertEquals(MODEL_FRESH, mm.openModel(URI_NONE)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + @Test + public void openModelIfPresentExist() { + assertEquals(MODEL_TWO, mm.openModelIfPresent(URI_TWO)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void openModelIfPresentNonExist() { + assertNull(mm.openModelIfPresent(URI_NONE)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void openModelStrictExist() { + assertEquals(MODEL_ONE, mm.openModel(URI_ONE, true)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void openModelNonStrictExist() { + assertEquals(MODEL_ONE, mm.openModel(URI_ONE, false)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void openModelNonStrictNonExist() { + assertEquals(MODEL_FRESH, mm.openModel(URI_NONE, false)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + @Test + public void removeModelExist() { + mm.removeModel(URI_ONE); + assertList(URI_TWO); + } + + @Test(expected = DoesNotExistException.class) + public void removeModelNonExist() { + mm.removeModel(URI_NONE); + } + + @Test + public void getModelExist() { + assertEquals(MODEL_TWO, mm.getModel(URI_TWO)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void getModelRigorousNonExist() { + rigorous(); + assertNull(mm.getModel(URI_NONE)); + assertList(URI_ONE, URI_TWO); + } + + @Test + public void getModelRelaxedNonExist() { + assertEquals(MODEL_FRESH, mm.getModel(URI_NONE)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + @Test + public void getModelLoadIfAbsentExist() { + assertEquals(MODEL_TWO, mm.getModel(URI_TWO, modelReader)); + assertList(URI_ONE, URI_TWO); + } + + @Test(expected = CannotCreateException.class) + public void getModelLoadIfAbsentRigorousNonExist() { + rigorous(); + mm.getModel(URI_NONE, modelReader); + } + + @Test + public void getModelLoadIfAbsentRelaxedNonExist() { + assertEquals(MODEL_FRESH, mm.getModel(URI_NONE, modelReader)); + assertList(URI_ONE, URI_TWO, URI_NONE); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + private void relaxed() { + mm = new ModelMakerWithPersistentEmptyModels(relaxed); + } + + private void rigorous() { + mm = new ModelMakerWithPersistentEmptyModels(rigorous); + } + + private void assertList(String... expectedArray) { + Set expected = new HashSet<>(Arrays.asList(expectedArray)); + Set actual = mm.listModels().toSet(); + assertEquals(expected, actual); + } + +}