diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/AbstractModelMakerDecorator.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/AbstractModelMakerDecorator.java new file mode 100644 index 000000000..8df8905ea --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/AbstractModelMakerDecorator.java @@ -0,0 +1,96 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess.adapters; + +import com.hp.hpl.jena.graph.GraphMaker; +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; + +/** + * Extend this to add decorator functionality to a ModelMaker. The sub-class can + * override specific methods as needed. + */ +public class AbstractModelMakerDecorator implements ModelMaker { + private final ModelMaker inner; + + public AbstractModelMakerDecorator(ModelMaker inner) { + if (inner == null) { + throw new NullPointerException("'inner' may not be null."); + } + this.inner = inner; + } + + @Override + public Model createDefaultModel() { + return inner.createDefaultModel(); + } + + @Override + public Model createFreshModel() { + return inner.createFreshModel(); + + } + + @Override + public Model openModel(String name) { + return inner.openModel(name); + } + + @Override + public Model openModelIfPresent(String string) { + return inner.openModelIfPresent(string); + } + + @Override + public Model getModel(String URL) { + return inner.getModel(URL); + } + + @Override + public Model getModel(String URL, ModelReader loadIfAbsent) { + return inner.getModel(URL, loadIfAbsent); + } + + @Override + public Model createModel(String name, boolean strict) { + return inner.createModel(name, strict); + } + + @Override + public Model createModel(String name) { + return inner.createModel(name); + } + + @Override + public Model openModel(String name, boolean strict) { + return inner.openModel(name, strict); + } + + @Override + public void removeModel(String name) { + inner.removeModel(name); + } + + @Override + public boolean hasModel(String name) { + return inner.hasModel(name); + } + + @Override + public void close() { + inner.close(); + } + + @Override + public GraphMaker getGraphMaker() { + return inner.getGraphMaker(); + } + + @Override + public ExtendedIterator listModels() { + return inner.listModels(); + } + +} 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 new file mode 100644 index 000000000..8503dd2de --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMaker.java @@ -0,0 +1,99 @@ +/* $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.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 keeps a cached list of the available models. + * + * 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. + * + * This is a useful decorator for some ModelMakers where listModels() is a + * costly operation. + */ +public class ListCachingModelMaker extends AbstractModelMakerDecorator { + private final Set modelNames; + + public ListCachingModelMaker(ModelMaker inner) { + super(inner); + this.modelNames = new TreeSet<>(inner.listModels().toSet()); + } + + @Override + public boolean hasModel(String name) { + return modelNames.contains(name); + + } + + @Override + public ExtendedIterator listModels() { + return WrappedIterator.create(modelNames.iterator()); + } + + @Override + public Model getModel(String URL) { + Model m = super.getModel(URL); + if (m != null) { + modelNames.add(URL); + } + return m; + } + + @Override + public Model getModel(String URL, ModelReader loadIfAbsent) { + Model m = super.getModel(URL, loadIfAbsent); + modelNames.add(URL); + return m; + } + + @Override + public Model createModel(String name, boolean strict) { + Model m = super.createModel(name, strict); + modelNames.add(name); + return m; + } + + @Override + public Model createModel(String name) { + return createModel(name, false); + } + + @Override + public Model openModel(String name) { + Model m = super.openModel(name); + modelNames.add(name); + return m; + } + + @Override + public Model openModelIfPresent(String name) { + Model m = super.openModelIfPresent(name); + if (m != null) { + modelNames.add(name); + } + return m; + } + + @Override + public Model openModel(String name, boolean strict) { + Model m = super.openModel(name, strict); + modelNames.add(name); + return m; + } + + @Override + public void removeModel(String name) { + super.removeModel(name); + modelNames.remove(name); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMaker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMaker.java new file mode 100644 index 000000000..9f9da1907 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMaker.java @@ -0,0 +1,106 @@ +/* $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.HashMap; +import java.util.Map; + +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.shared.AlreadyExistsException; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.ModelSynchronizer; +import edu.cornell.mannlib.vitro.webapp.rdfservice.adapters.VitroModelFactory; + +/** + * Provides fast read access to small models, by creating a "mapped" model in + * memory. + * + * When updates are detected on the "mapped" model, they are propagated to the + * base model. + */ +public class MemoryMappingModelMaker extends AbstractModelMakerDecorator { + private final Map mappedModels; + + public MemoryMappingModelMaker(ModelMaker inner, + String... modelUrisForMapping) { + super(inner); + + this.mappedModels = new HashMap<>(); + for (String name : modelUrisForMapping) { + mappedModels.put(name, createMemoryMapping(name)); + } + } + + private Model createMemoryMapping(String name) { + Model externalModel = super.openModel(name); + Model memoryModel = VitroModelFactory.createModel(); + memoryModel.add(externalModel); + memoryModel.register(new ModelSynchronizer(externalModel, name)); + return memoryModel; + } + + private boolean isMapped(String name) { + return mappedModels.containsKey(name); + } + + private Model getMapped(String name) { + return mappedModels.get(name); + } + + @Override + public Model openModel(String name) { + return isMapped(name) ? getMapped(name) : super.openModel(name); + } + + @Override + public Model openModelIfPresent(String name) { + return isMapped(name) ? getMapped(name) : super + .openModelIfPresent(name); + } + + @Override + public Model getModel(String name) { + return isMapped(name) ? getMapped(name) : super.getModel(name); + } + + @Override + public Model getModel(String name, ModelReader loadIfAbsent) { + return isMapped(name) ? getMapped(name) : super.getModel(name, + loadIfAbsent); + } + + @Override + public Model createModel(String name, boolean strict) { + if (isMapped(name)) { + if (strict) { + throw new AlreadyExistsException(name); + } else { + return getMapped(name); + } + } else { + return super.createModel(name, strict); + } + } + + @Override + public Model createModel(String name) { + return isMapped(name) ? getMapped(name) : super.createModel(name); + } + + @Override + public Model openModel(String name, boolean strict) { + return isMapped(name) ? getMapped(name) : super.openModel(name, strict); + } + + @Override + public void removeModel(String name) { + if (isMapped(name)) { + Model m = mappedModels.remove(name); + m.close(); + } + super.removeModel(name); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/NamedDefaultModelMaker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/NamedDefaultModelMaker.java new file mode 100644 index 000000000..7ae63450a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/NamedDefaultModelMaker.java @@ -0,0 +1,121 @@ +/* $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.Collections; + +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.ModelMaker; +import com.hp.hpl.jena.rdf.model.ModelReader; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; + +/** + * This model maker allows you to refer to the default model by a name. + */ +public class NamedDefaultModelMaker extends AbstractModelMakerDecorator { + private static final Log log = LogFactory + .getLog(NamedDefaultModelMaker.class); + + private final String defaultModelUri; + + public NamedDefaultModelMaker(ModelMaker inner, String defaultModelUri) { + super(inner); + this.defaultModelUri = defaultModelUri; + } + + private boolean isDefaultModel(String name) { + return name != null && name.equals(defaultModelUri); + } + + @Override + public Model openModel(String name) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.openModel(name); + } + } + + @Override + public Model openModelIfPresent(String name) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.openModelIfPresent(name); + } + } + + @Override + public Model getModel(String name) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.getModel(name); + } + } + + @Override + public Model getModel(String name, ModelReader loadIfAbsent) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.getModel(name, loadIfAbsent); + } + } + + @Override + public Model createModel(String name, boolean strict) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.createModel(name, strict); + } + } + + @Override + public Model createModel(String name) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.createModel(name); + } + } + + @Override + public Model openModel(String name, boolean strict) { + if (isDefaultModel(name)) { + return super.createDefaultModel(); + } else { + return super.openModel(name, strict); + } + } + + @Override + public void removeModel(String name) { + if (isDefaultModel(name)) { + log.warn("Attempting to remove the default model."); + } else { + super.removeModel(name); + } + + } + + @Override + public boolean hasModel(String name) { + if (isDefaultModel(name)) { + return true; + } else { + return super.hasModel(name); + } + } + + @Override + public ExtendedIterator listModels() { + return super.listModels().andThen( + Collections.singleton(this.defaultModelUri).iterator()); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ShadowingModelMaker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ShadowingModelMaker.java new file mode 100644 index 000000000..a755fb79a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ShadowingModelMaker.java @@ -0,0 +1,140 @@ +/* $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.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.hp.hpl.jena.graph.GraphMaker; +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; + +/** + * Allow some models in the "shadowing" ModelMaker to hide the corresponding + * models in the "shadowed" ModelMaker. + * + * Specify both ModelMakers, and the list of URIs for the shadowing models. + */ +public class ShadowingModelMaker extends AbstractModelMakerDecorator { + private final ModelMaker shadowing; + private final Set shadowUris; + + public ShadowingModelMaker(ModelMaker shadowed, ModelMaker shadowing, + String... shadowUris) { + super(shadowed); + this.shadowing = shadowing; + this.shadowUris = new HashSet<>(Arrays.asList(shadowUris)); + } + + private boolean isShadow(String name) { + return shadowUris.contains(name); + } + + @Override + public Model createDefaultModel() { + return super.createDefaultModel(); + } + + @Override + public Model createFreshModel() { + return super.createFreshModel(); + } + + @Override + public Model openModel(String name) { + if (isShadow(name)) { + return shadowing.openModel(name); + } else { + return super.openModel(name); + } + } + + @Override + public Model openModelIfPresent(String name) { + if (isShadow(name)) { + return shadowing.openModelIfPresent(name); + } else { + return super.openModelIfPresent(name); + } + } + + @Override + public Model getModel(String name) { + if (isShadow(name)) { + return shadowing.getModel(name); + } else { + return super.getModel(name); + } + } + + @Override + public Model getModel(String name, ModelReader loadIfAbsent) { + if (isShadow(name)) { + return shadowing.getModel(name, loadIfAbsent); + } else { + return super.getModel(name, loadIfAbsent); + } + } + + @Override + public Model createModel(String name, boolean strict) { + if (isShadow(name)) { + return shadowing.createModel(name, strict); + } else { + return super.createModel(name, strict); + } + } + + @Override + public Model createModel(String name) { + if (isShadow(name)) { + return shadowing.createModel(name); + } else { + return super.createModel(name); + } + } + + @Override + public Model openModel(String name, boolean strict) { + if (isShadow(name)) { + return shadowing.openModel(name, strict); + } else { + return super.openModel(name, strict); + } + } + + @Override + public void removeModel(String name) { + if (isShadow(name)) { + shadowing.removeModel(name); + } else { + super.removeModel(name); + } + } + + @Override + public boolean hasModel(String name) { + if (isShadow(name)) { + return shadowing.hasModel(name); + } else { + return super.hasModel(name); + } + } + + @Override + public void close() { + shadowing.close(); + super.close(); + } + + @Override + public ExtendedIterator listModels() { + Set allNames = super.listModels().toSet(); + allNames.addAll(shadowUris); + return WrappedIterator.create(allNames.iterator()); + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMaker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMaker.java new file mode 100644 index 000000000..0ad156a54 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMaker.java @@ -0,0 +1,249 @@ +/* $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.HashMap; +import java.util.Map; + +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.shared.AlreadyExistsException; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.adapters.VitroModelFactory; + +/** + * This ModelMaker decorator creates one or more "union models" over the models + * provided to it by the inner ModelMaker. + * + * Each union model contains all of the triples of both its base model and its + * "plus" model. Any changes to the union model are delegated to the base model. + * If changes are desired in the "plus" model, it must be accessed directly. + * + * This can create surprises, since the union model will claim to have a given + * statement that is part of the plus model, but an attempt to delete that + * statement from the union model has no effect. + */ +public class UnionModelsModelMaker extends AbstractModelMakerDecorator { + private final Map unionModelsMap; + + /** + * Create it like this: + * + *
+	 * new UnionModelsModelMaker(inner,
+	 *     UnionSpec.base("baseUri").plus("plusUri").yields("unionUri"),
+	 *     ...);
+	 * 
+ */ + public UnionModelsModelMaker(ModelMaker inner, UnionSpec... unionModelSpecs) { + super(inner); + + this.unionModelsMap = new HashMap<>(); + + for (UnionSpec spec : unionModelSpecs) { + String unionUri = spec.getUnionUri(); + if (unionModelsMap.containsKey(unionUri)) { + throw new IllegalArgumentException( + "Two UnionSpecs may not have the same union URI: " + + spec + ", " + unionModelsMap.get(unionUri)); + } + this.unionModelsMap.put(unionUri, spec); + } + + for (UnionSpec spec1 : unionModelsMap.values()) { + if (unionModelsMap.containsKey(spec1.getBaseUri()) + || unionModelsMap.containsKey(spec1.getPlusUri())) { + throw new IllegalArgumentException( + "A UnionSpec may not build on another UnionSpec: " + + spec1); + } + } + } + + private boolean hasUnionModel(String name) { + return unionModelsMap.containsKey(name); + } + + /** + * The union models use lazy initialization, so there is no overhead if the + * model is never requested. + */ + private Model getUnionModel(String name) { + UnionSpec spec = unionModelsMap.get(name); + synchronized (spec) { + if (spec.getUnionModel() == null) { + Model baseModel = super.openModel(spec.getBaseUri()); + Model plusModel = super.openModel(spec.getPlusUri()); + spec.setUnionModel(VitroModelFactory.createUnion(baseModel, + plusModel)); + } + } + return spec.getUnionModel(); + } + + // ---------------------------------------------------------------------- + // Overridden methods. + // ---------------------------------------------------------------------- + + @Override + public Model createModel(String name) { + return createModel(name, false); + } + + @Override + public Model createModel(String name, boolean strict) { + if (hasUnionModel(name)) { + if (strict) { + throw new AlreadyExistsException(name); + } else { + return getUnionModel(name); + } + } else { + return super.createModel(name, strict); + } + } + + @Override + public Model openModel(String name, boolean strict) { + if (hasUnionModel(name)) { + return getUnionModel(name); + } else { + return super.openModel(name, strict); + } + } + + @Override + public Model openModel(String name) { + if (hasUnionModel(name)) { + return getUnionModel(name); + } else { + return super.openModel(name); + } + } + + @Override + public Model openModelIfPresent(String name) { + if (hasUnionModel(name)) { + return getUnionModel(name); + } else { + return super.openModelIfPresent(name); + } + } + + @Override + public boolean hasModel(String name) { + if (hasUnionModel(name)) { + return true; + } else { + return super.hasModel(name); + } + } + + @Override + public ExtendedIterator listModels() { + return super.listModels().andThen(unionModelsMap + .keySet().iterator()); + } + + @Override + public void removeModel(String name) { + if (hasUnionModel(name)) { + unionModelsMap.remove(name); + } else { + super.removeModel(name); + } + } + + @Override + public Model getModel(String URL) { + if (hasUnionModel(URL)) { + return getUnionModel(URL); + } else { + return super.getModel(URL); + } + } + + @Override + public Model getModel(String URL, ModelReader loadIfAbsent) { + if (hasUnionModel(URL)) { + return getUnionModel(URL); + } else { + return super.getModel(URL, loadIfAbsent); + } + } + + // ---------------------------------------------------------------------- + // UnionSpec and builder classes. + // ---------------------------------------------------------------------- + + public static class UnionSpec { + public static UnionSpecBase base(String baseUri) { + return new UnionSpecBase(baseUri); + } + + private final String baseUri; + private final String plusUri; + private final String unionUri; + private Model unionModel; + + public UnionSpec(String baseUri, String plusUri, String unionUri) { + this.baseUri = baseUri; + this.plusUri = plusUri; + this.unionUri = unionUri; + } + + public Model getUnionModel() { + return unionModel; + } + + public void setUnionModel(Model unionModel) { + this.unionModel = unionModel; + } + + public String getBaseUri() { + return baseUri; + } + + public String getPlusUri() { + return plusUri; + } + + public String getUnionUri() { + return unionUri; + } + + @Override + public String toString() { + return "UnionSpec[baseUri=" + baseUri + ", plusUri=" + plusUri + + ", unionUri=" + unionUri + "]"; + } + } + + public static class UnionSpecBase { + private final String baseUri; + + UnionSpecBase(String baseUri) { + this.baseUri = baseUri; + } + + public UnionSpecPair plus(String plusUri) { + return new UnionSpecPair(baseUri, plusUri); + } + } + + public static class UnionSpecPair { + private final String baseUri; + private final String plusUri; + + public UnionSpecPair(String baseUri, String plusUri) { + this.baseUri = baseUri; + this.plusUri = plusUri; + } + + public UnionSpec yields(String unionUri) { + return new UnionSpec(baseUri, plusUri, unionUri); + } + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMakerTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMakerTest.java new file mode 100644 index 000000000..7f3013d5c --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/ListCachingModelMakerTest.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 static org.junit.Assert.fail; + +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; + +/** + * TODO + */ +public class ListCachingModelMakerTest 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 ListCachingModelMaker(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 ListCachingModelMaker(relaxed); + } + + private void rigorous() { + mm = new ListCachingModelMaker(rigorous); + } + + private void assertList(String... expectedArray) { + Set expected = new HashSet<>(Arrays.asList(expectedArray)); + Set actual = mm.listModels().toSet(); + assertEquals(expected, actual); + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMakerTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMakerTest.java new file mode 100644 index 000000000..28803c097 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/MemoryMappingModelMakerTest.java @@ -0,0 +1,174 @@ +/* $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 java.io.StringReader; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +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.graph.Graph; +import com.hp.hpl.jena.graph.impl.CollectionGraph; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; +import com.hp.hpl.jena.rdf.model.Statement; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; + +/** + * TODO + */ +public class MemoryMappingModelMakerTest extends AbstractTestClass { + private static final String URI_MAPPED = "http://memory.mapped.model"; + private static final String URI_UNMAPPED = "http://unmapped.model"; + private static final String MODEL_CONTENTS = "@prefix : . \n" + + ":a :b :c ."; + + private GraphModelStructure unmapped; + private GraphModelStructure mapped; + private ModelMakerStub innerModelMaker; + private MemoryMappingModelMaker mmmm; + + @Before + public void setup() { + unmapped = new GraphModelStructure(URI_UNMAPPED, MODEL_CONTENTS); + mapped = new GraphModelStructure(URI_MAPPED, MODEL_CONTENTS); + + innerModelMaker = ModelMakerStub.rigorous(createModel(), createModel()); + innerModelMaker.put(mapped.uri, mapped.model); + innerModelMaker.put(unmapped.uri, unmapped.model); + + mmmm = new MemoryMappingModelMaker(innerModelMaker, mapped.uri); + + unmapped.methodCalls.clear(); + mapped.methodCalls.clear(); + } + + // ---------------------------------------------------------------------- + // tests + // ---------------------------------------------------------------------- + + @Test + public void unmappedRead() { + assertModelContents(unmapped, "[http://z#a, http://z#b, http://z#c]"); + assertMethodCalls(unmapped, "find"); + } + + @Test + public void mappedRead() { + assertModelContents(mapped, "[http://z#a, http://z#b, http://z#c]"); + assertMethodCalls(mapped); + } + + @Test + public void unmappedWrite() { + mmmm.openModel(URI_UNMAPPED).add(newStatement()); + assertModelContents(unmapped, "[http://z#a, http://z#b, http://z#c]", + "[http://z#new, http://z#to, http://z#you]"); + assertMethodCalls(unmapped, "add", "find"); + } + + @Test + public void mappedWrite() { + mmmm.openModel(URI_MAPPED).add(newStatement()); + assertModelContents(mapped, "[http://z#a, http://z#b, http://z#c]", + "[http://z#new, http://z#to, http://z#you]"); + assertMethodCalls(mapped, "add"); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + private static Model createModel() { + return ModelFactory.createDefaultModel(); + } + + private void assertModelContents(GraphModelStructure gms, + String... expected) { + Set stmts = mmmm.openModel(gms.uri).listStatements().toSet(); + assertStatements(stmts, expected); + } + + private void assertStatements(Set stmts, String... expected) { + Set actual = new HashSet<>(); + for (Statement stmt : stmts) { + actual.add(stmt.toString()); + } + assertEquals(new HashSet<>(Arrays.asList(expected)), actual); + } + + private void assertMethodCalls(GraphModelStructure gms, String... expected) { + assertEquals(Arrays.asList(expected), gms.methodCalls); + } + + public Statement newStatement() { + Resource s = ResourceFactory.createResource("http://z#new"); + Property p = ResourceFactory.createProperty("http://z#to"); + Resource o = ResourceFactory.createResource("http://z#you"); + return ResourceFactory.createStatement(s, p, o); + } + + // ---------------------------------------------------------------------- + // Helper classes + // ---------------------------------------------------------------------- + + private static class GraphModelStructure { + final String uri; + final Graph graph; + final List methodCalls; + final RecordingInvocationHandler handler; + final Graph proxy; + final Model model; + + public GraphModelStructure(String uri, String contents) { + this.uri = uri; + graph = new CollectionGraph(); + methodCalls = new ArrayList<>(); + handler = new RecordingInvocationHandler(graph, methodCalls); + proxy = wrapGraph(); + model = ModelFactory.createModelForGraph(proxy); + model.read(new StringReader(contents), null, "TURTLE"); + } + + private Graph wrapGraph() { + ClassLoader classLoader = Model.class.getClassLoader(); + Class[] interfaces = new Class[] { Graph.class }; + return (Graph) Proxy.newProxyInstance(classLoader, interfaces, + handler); + } + } + + private static class RecordingInvocationHandler implements + InvocationHandler { + private final Object inner; + private final List methodCalls; + + public RecordingInvocationHandler(Object inner, List methodCalls) { + this.inner = inner; + this.methodCalls = methodCalls; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + methodCalls.add(method.getName()); + return method.invoke(inner, args); + } + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMakerTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMakerTest.java new file mode 100644 index 000000000..dc5ee5a72 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/modelaccess/adapters/UnionModelsModelMakerTest.java @@ -0,0 +1,259 @@ +/* $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.shared.AlreadyExistsException; +import com.hp.hpl.jena.shared.CannotCreateException; +import com.hp.hpl.jena.shared.DoesNotExistException; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.UnionModelsModelMaker.UnionSpec; + +/** + * Test the functionality of the UnionModelsModelMaker. + */ +public class UnionModelsModelMakerTest 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_THREE = "http://model.three"; + private static final String URI_UNION = "http://model.union"; + 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_THREE = createModel(); + private static final Model MODEL_DEFAULT = createModel(); + private static final Model MODEL_FRESH = createModel(); + + private static Model createModel() { + return ModelFactory.createDefaultModel(); + } + + private ModelMaker inner; + private ModelMaker mm; + + @Before + public void setup() { + /* + * Use a rigorous inner model maker, but it doesn't make much difference. + */ + inner = ModelMakerStub.rigorous(MODEL_DEFAULT, MODEL_FRESH) + .put(URI_ONE, MODEL_ONE).put(URI_TWO, MODEL_TWO) + .put(URI_THREE, MODEL_THREE); + + mm = new UnionModelsModelMaker(inner, UnionSpec.base(URI_ONE) + .plus(URI_TWO).yields(URI_UNION)); + } + + @SuppressWarnings("unused") + @Test(expected = NullPointerException.class) + public void nullInnerModel() { + new UnionModelsModelMaker(null, UnionSpec.base(URI_ONE).plus(URI_TWO) + .yields(URI_UNION)); + } + + @SuppressWarnings("unused") + @Test(expected = IllegalArgumentException.class) + public void duplicateUnionUri() { + new UnionModelsModelMaker(inner, UnionSpec.base(URI_ONE).plus(URI_TWO) + .yields(URI_UNION), UnionSpec.base(URI_ONE).plus(URI_THREE) + .yields(URI_UNION)); + } + + @SuppressWarnings("unused") + @Test(expected = IllegalArgumentException.class) + public void nestedUnions() { + new UnionModelsModelMaker(inner, UnionSpec.base(URI_ONE).plus(URI_TWO) + .yields(URI_UNION), UnionSpec.base(URI_UNION).plus(URI_THREE) + .yields("http://nestedUnion")); + } + + @Test + public void hasModelActual() { + assertTrue(mm.hasModel(URI_ONE)); + } + + @Test + public void hasModelNone() { + assertFalse(mm.hasModel(URI_NONE)); + } + + @Test + public void hasModelUnion() { + assertTrue(mm.hasModel(URI_UNION)); + } + + @Test + public void listModels() { + assertExpectedModelsList(URI_ONE, URI_TWO, URI_THREE, URI_UNION); + } + + @Test + public void createModelActual() { + assertEquals(MODEL_ONE, mm.createModel(URI_ONE)); + } + + @Test + public void createModelNone() { + assertEquals(MODEL_FRESH, mm.createModel(URI_NONE)); + } + + @Test + public void createModelUnion() { + assertTrue(isUnionModel(mm.createModel(URI_UNION))); + } + + @Test(expected = AlreadyExistsException.class) + public void createModelActualStrict() { + mm.createModel(URI_ONE, true); + } + + @Test + public void createModelNoneStrict() { + assertEquals(MODEL_FRESH, mm.createModel(URI_NONE, true)); + } + + @Test(expected = AlreadyExistsException.class) + public void createModelUnionStrict() { + mm.createModel(URI_UNION, true); + } + + @Test + public void openModelActual() { + assertEquals(MODEL_ONE, mm.openModel(URI_ONE)); + } + + @Test(expected = DoesNotExistException.class) + public void openModelNone() { + mm.openModel(URI_NONE); + } + + @Test + public void openModelUnion() { + assertTrue(isUnionModel(mm.openModel(URI_UNION))); + } + + @Test + public void openModelActualStrict() { + assertEquals(MODEL_ONE, mm.openModel(URI_ONE, true)); + } + + @Test(expected = DoesNotExistException.class) + public void openModelNoneStrict() { + mm.openModel(URI_NONE, true); + } + + @Test + public void openModelUnionStrict() { + assertTrue(isUnionModel(mm.openModel(URI_UNION, true))); + } + + @Test + public void openModelIfPresentActual() { + assertEquals(MODEL_ONE, mm.openModelIfPresent(URI_ONE)); + } + + @Test + public void openModelIfPresentNone() { + assertNull(mm.openModelIfPresent(URI_NONE)); + } + + @Test + public void openModelIfPresentUnion() { + assertTrue(isUnionModel(mm.openModelIfPresent(URI_UNION))); + } + + @Test + public void removeModelActual() { + mm.removeModel(URI_ONE); + assertExpectedModelsList(URI_TWO, URI_THREE, URI_UNION); + } + + @Test(expected = DoesNotExistException.class) + public void removeModelNone() { + mm.removeModel(URI_NONE); + } + + @Test + public void removeModelUnion() { + mm.removeModel(URI_UNION); + assertExpectedModelsList(URI_ONE, URI_TWO, URI_THREE); + } + + @Test + public void getModelActual() { + assertEquals(MODEL_ONE, mm.getModel(URI_ONE)); + } + + @Test + public void getModelNone() { + assertEquals(null, mm.getModel(URI_NONE)); + } + + @Test + public void getModelUnion() { + assertTrue(isUnionModel(mm.getModel(URI_UNION))); + } + + @Test + public void getModelLoadIfAbsentActual() { + assertEquals(MODEL_ONE, mm.getModel(URI_ONE, null)); + } + + @Test(expected = CannotCreateException.class) + public void getModelLoadIfAbsentNone() { + mm.getModel(URI_NONE, null); + } + + @Test + public void getModelLoadIfAbsentUnion() { + assertTrue(isUnionModel(mm.getModel(URI_UNION, null))); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + /** + * No easy way to assert that this is actually the union model, but we can + * assert that it is not null, and not any model we know of. + */ + private boolean isUnionModel(Model m) { + Model[] knownModels = { MODEL_ONE, MODEL_TWO, MODEL_THREE, + MODEL_DEFAULT, MODEL_FRESH }; + if (m == null) { + return false; + } + + for (Model knownModel : knownModels) { + if (m == knownModel) { + return false; + } + } + + return true; + } + + private void assertExpectedModelsList(String... uris) { + Set expected = new HashSet<>(Arrays.asList(uris)); + assertEquals(expected, mm.listModels().toSet()); + } + +} \ No newline at end of file diff --git a/webapp/test/stubs/com/hp/hpl/jena/rdf/model/ModelMaker/ModelMakerStub.java b/webapp/test/stubs/com/hp/hpl/jena/rdf/model/ModelMaker/ModelMakerStub.java new file mode 100644 index 000000000..cbcd80f72 --- /dev/null +++ b/webapp/test/stubs/com/hp/hpl/jena/rdf/model/ModelMaker/ModelMakerStub.java @@ -0,0 +1,223 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package stubs.com.hp.hpl.jena.rdf.model.ModelMaker; + +import java.util.HashMap; +import java.util.Map; + +import com.hp.hpl.jena.graph.GraphMaker; +import com.hp.hpl.jena.graph.impl.SimpleGraphMaker; +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.shared.AlreadyExistsException; +import com.hp.hpl.jena.shared.CannotCreateException; +import com.hp.hpl.jena.shared.DoesNotExistException; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; +import com.hp.hpl.jena.util.iterator.WrappedIterator; + +/** + * A ModelMaker stub, but is it strict or relaxed? Choose one of the + * sub-classes. + * + * The only difference between strict and relaxed is on a call to + * openModel(name), when no such model exists. The relaxed ModelMaker will + * create a fresh model and associate it with the name. The strict modelMaker + * will throw a DoesNotExistException. + * + * Warning: the "fresh model" is the same every time, so calling + * createFreshModel() more than once during a test will give illusory results. + */ +public abstract class ModelMakerStub implements ModelMaker { + protected final Model defaultModel; + protected final Model freshModel; + protected final Map models = new HashMap<>(); + protected final GraphMaker graphMaker = new SimpleGraphMaker(); + + // ---------------------------------------------------------------------- + // Factory methods + // ---------------------------------------------------------------------- + + public static ModelMakerStub rigorous(Model defaultModel, Model freshModel) { + return new ModelMakerRigorousStub(defaultModel, freshModel); + } + + public static ModelMakerStub relaxed(Model defaultModel, Model freshModel) { + return new ModelMakerRelaxedStub(defaultModel, freshModel); + } + + // ---------------------------------------------------------------------- + // The abstract class + // ---------------------------------------------------------------------- + + protected ModelMakerStub(Model defaultModel, Model freshModel) { + this.defaultModel = defaultModel; + this.freshModel = freshModel; + } + + public ModelMakerStub put(String uri, Model model) { + models.put(uri, model); + return this; + } + + @Override + public GraphMaker getGraphMaker() { + return graphMaker; + } + + @Override + public void close() { + // Nothing to close. + } + + @Override + public boolean hasModel(String name) { + return models.containsKey(name); + } + + @Override + public ExtendedIterator listModels() { + return WrappedIterator.create(models.keySet().iterator()); + } + + @Override + public Model createModel(String name) { + return createModel(name, false); + } + + @Override + public Model createModel(String name, boolean strict) { + if (hasModel(name)) { + if (strict) { + throw new AlreadyExistsException(name); + } else { + return models.get(name); + } + } + return freshModel; + } + + @Override + public Model createDefaultModel() { + return defaultModel; + } + + @Override + public Model createFreshModel() { + return freshModel; + } + + @Override + public Model openModel(String name, boolean strict) { + if (strict && !hasModel(name)) { + throw new DoesNotExistException(name); + } else { + return openModel(name); + } + } + + @Override + public Model openModelIfPresent(String name) { + return models.get(name); + } + + @Override + public void removeModel(String name) { + if (hasModel(name)) { + models.remove(name); + } else { + throw new DoesNotExistException(name); + } + } + + // ---------------------------------------------------------------------- + // Concrete sub-classes + // ---------------------------------------------------------------------- + + /** + * "Relaxed" means that if they ask for a model that doesn't exist, we + * create one. + * + * Note: should return a new model, instead of the "fresh" model. + */ + private static class ModelMakerRelaxedStub extends ModelMakerStub { + public ModelMakerRelaxedStub(Model defaultModel, Model freshModel) { + super(defaultModel, freshModel); + } + + @Override + public Model openModel(String name) { + if (hasModel(name)) { + return models.get(name); + } else { + return freshModel; + } + } + + @Override + public Model getModel(String name) { + if (hasModel(name)) { + return models.get(name); + } else { + return freshModel; + } + } + + /** + * TODO: Rather than having this part of "relaxed" or "rigorous", the + * result should depend on the ModelReader. + */ + @Override + public Model getModel(String name, ModelReader loadIfAbsent) { + if (hasModel(name)) { + return models.get(name); + } else { + return freshModel; + } + } + + } + + /** + * "Rigorous" means that if they ask for a model that doesn't exist, we + * return null or throw an exception. + */ + private static class ModelMakerRigorousStub extends ModelMakerStub { + public ModelMakerRigorousStub(Model defaultModel, Model freshModel) { + super(defaultModel, freshModel); + } + + @Override + public Model openModel(String name) { + if (hasModel(name)) { + return models.get(name); + } else { + throw new DoesNotExistException(name); + } + } + + @Override + public Model getModel(String name) { + if (hasModel(name)) { + return models.get(name); + } else { + return null; + } + } + + /** + * TODO: Rather than having this part of "relaxed" or "rigorous", the + * result should depend on the ModelReader. + */ + @Override + public Model getModel(String name, ModelReader loadIfAbsent) { + if (hasModel(name)) { + return models.get(name); + } else { + throw new CannotCreateException(name); + } + } + + } + +}