VIVO-823 Create some tests for the VitroModelFactory

Make changes as determined by the tests, to BulkUpdatingOntModel as well.
Add debug statements to ModelSynchronizer.
This commit is contained in:
Jim Blake 2014-07-18 17:04:58 -04:00
parent 04f763109e
commit 63ed82cef9
5 changed files with 982 additions and 59 deletions

View file

@ -3,79 +3,115 @@
package edu.cornell.mannlib.vitro.webapp.dao.jena; package edu.cornell.mannlib.vitro.webapp.dao.jena;
import java.util.List; import java.util.List;
import java.util.Set;
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.Model;
import com.hp.hpl.jena.rdf.model.ModelChangedListener; import com.hp.hpl.jena.rdf.model.ModelChangedListener;
import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.rdf.model.impl.StmtIteratorImpl;
import edu.cornell.mannlib.vitro.webapp.dao.jena.event.CloseEvent; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.CloseEvent;
/** /**
* Simple change listener to keep a model (the 'synchronizee') in synch with the model with which it is registered. * Simple change listener to keep a model (the 'synchronizee') in synch with the
* model with which it is registered.
*
* @author bjl23 * @author bjl23
* *
*/ */
public class ModelSynchronizer implements ModelChangedListener { public class ModelSynchronizer implements ModelChangedListener {
private static final Log log = LogFactory.getLog(ModelSynchronizer.class);
private Model m; private Model m;
private String hash;
public ModelSynchronizer (Model synchronizee) {
public ModelSynchronizer(Model synchronizee, String name) {
this.m = synchronizee; this.m = synchronizee;
} this.hash = Integer.toHexString(this.hashCode());
log.debug(String.format("create: %s, wraps %s(%s) as %s", hash, this.m
public void addedStatement(Statement arg0) { .getClass().getName(), Integer.toHexString(this.m.hashCode()),
m.add(arg0); name));
} }
public void addedStatements(Statement[] arg0) { @Override
m.add(arg0); public void addedStatement(Statement s) {
log.debug(hash + " addedStatement" + s);
m.add(s);
} }
@Override
public void addedStatements(List arg0) { public void addedStatements(Statement[] statements) {
m.add(arg0); log.debug(hash + " addedStatements: " + statements.length);
m.add(statements);
} }
@Override
public void addedStatements(StmtIterator arg0) { public void addedStatements(List<Statement> statements) {
m.add(arg0); log.debug(hash + " addedStatements: " + statements.size());
m.add(statements);
} }
@Override
public void addedStatements(Model arg0) { public void addedStatements(StmtIterator statements) {
m.add(arg0); if (log.isDebugEnabled()) {
Set<Statement> set = statements.toSet();
log.debug(hash + " addedStatements: " + set.size());
m.add(new StmtIteratorImpl(set.iterator()));
} else {
m.add(new StmtIteratorImpl(statements));
}
} }
@Override
public void notifyEvent(Model arg0, Object arg1) { public void addedStatements(Model model) {
if ( arg1 instanceof CloseEvent ) { log.debug(hash + " addedStatements: " + model.size());
m.add(model);
}
@Override
public void notifyEvent(Model model, Object event) {
if (event instanceof CloseEvent) {
m.close(); m.close();
} }
} }
@Override
public void removedStatement(Statement arg0) { public void removedStatement(Statement s) {
m.remove(arg0); log.debug(hash + " removedStatement" + s);
m.remove(s);
} }
@Override
public void removedStatements(Statement[] arg0) { public void removedStatements(Statement[] statements) {
m.remove(arg0); log.debug(hash + " removedStatements: " + statements.length);
} m.remove(statements);
public void removedStatements(List arg0) {
m.remove(arg0);
} }
@Override
public void removedStatements(StmtIterator arg0) { public void removedStatements(List<Statement> statements) {
m.remove(arg0); log.debug(hash + " removedStatements: " + statements.size());
m.remove(statements);
} }
@Override
public void removedStatements(Model arg0) { public void removedStatements(StmtIterator statements) {
m.remove(arg0); if (log.isDebugEnabled()) {
Set<Statement> set = statements.toSet();
log.debug(hash + " removedStatements: " + set.size());
m.remove(new StmtIteratorImpl(set.iterator()));
} else {
m.remove(new StmtIteratorImpl(statements));
}
} }
@Override
public void removedStatements(Model model) {
log.debug(hash + " removedStatements: " + model.size());
m.remove(model);
}
} }

View file

@ -9,8 +9,14 @@ import java.net.URL;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.graph.BulkUpdateHandler; import com.hp.hpl.jena.graph.BulkUpdateHandler;
import com.hp.hpl.jena.graph.Graph;
import com.hp.hpl.jena.graph.Triple; import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.graph.impl.GraphWithPerform;
import com.hp.hpl.jena.graph.impl.WrappedBulkUpdateHandler;
import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.ModelFactory;
@ -27,13 +33,31 @@ import com.hp.hpl.jena.util.iterator.Map1;
* BulkUpdateHandler. * BulkUpdateHandler.
*/ */
public class BulkUpdatingOntModel extends AbstractOntModelDecorator { public class BulkUpdatingOntModel extends AbstractOntModelDecorator {
private static final Log log = LogFactory
.getLog(BulkUpdatingOntModel.class);
private static final RDFReaderF readerFactory = new RDFReaderFImpl(); private static final RDFReaderF readerFactory = new RDFReaderFImpl();
private final BulkUpdateHandler buh; private final BulkUpdateHandler buh;
public BulkUpdatingOntModel(OntModel inner, BulkUpdateHandler buh) { public BulkUpdatingOntModel(OntModel inner) {
super(inner); super(inner);
this.buh = buh; this.buh = inner.getGraph().getBulkUpdateHandler();
}
@SuppressWarnings("deprecation")
private static BulkUpdateHandler getWrappedBulkUpdateHandler(Graph graph) {
if (graph instanceof GraphWithPerform) {
return new WrappedBulkUpdateHandler((GraphWithPerform) graph,
graph.getBulkUpdateHandler());
} else {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.warn("Graph is not an instance of GraphWithPerform", e);
}
return graph.getBulkUpdateHandler();
}
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")

View file

@ -4,16 +4,25 @@ package edu.cornell.mannlib.vitro.webapp.rdfservice.adapters;
import static com.hp.hpl.jena.ontology.OntModelSpec.OWL_MEM; import static com.hp.hpl.jena.ontology.OntModelSpec.OWL_MEM;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.graph.BulkUpdateHandler; import com.hp.hpl.jena.graph.BulkUpdateHandler;
import com.hp.hpl.jena.graph.Graph; import com.hp.hpl.jena.graph.Graph;
import com.hp.hpl.jena.graph.compose.Union;
import com.hp.hpl.jena.graph.impl.WrappedBulkUpdateHandler;
import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.impl.OntModelImpl;
import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.impl.ModelCom;
/** /**
* Make models that will do proper bulk updates. * Make models that will do proper bulk updates.
*/ */
public class VitroModelFactory { public class VitroModelFactory {
private static final Log log = LogFactory.getLog(VitroModelFactory.class);
public static Model createModel() { public static Model createModel() {
return ModelFactory.createDefaultModel(); return ModelFactory.createDefaultModel();
} }
@ -21,38 +30,54 @@ public class VitroModelFactory {
public static OntModel createOntologyModel() { public static OntModel createOntologyModel() {
return ModelFactory.createOntologyModel(OWL_MEM); return ModelFactory.createOntologyModel(OWL_MEM);
} }
public static OntModel createOntologyModel(Model model) {
@SuppressWarnings("deprecation")
BulkUpdateHandler buh = model.getGraph().getBulkUpdateHandler();
OntModel ontModel = ModelFactory.createOntologyModel(OWL_MEM, model); public static OntModel createOntologyModel(Model model) {
return new BulkUpdatingOntModel(ontModel, buh); Graph graph = model.getGraph();
Model bareModel = new ModelCom(graph);
OntModel ontModel = new OntModelImpl(OWL_MEM, bareModel);
return new BulkUpdatingOntModel(ontModel);
} }
public static Model createUnion(Model baseModel, Model otherModel) { public static Model createUnion(Model baseModel, Model plusModel) {
@SuppressWarnings("deprecation") Graph baseGraph = baseModel.getGraph();
BulkUpdateHandler buh = baseModel.getGraph().getBulkUpdateHandler(); Graph plusGraph = plusModel.getGraph();
BulkUpdatingUnion unionGraph = new BulkUpdatingUnion(baseGraph,
plusGraph);
Model unionModel = ModelFactory.createUnion(baseModel, otherModel); BulkUpdateHandler buh = getBulkUpdateHandler(unionGraph);
Model unionModel = ModelFactory.createModelForGraph(unionGraph);
return new BulkUpdatingModel(unionModel, buh); return new BulkUpdatingModel(unionModel, buh);
} }
public static OntModel createUnion(OntModel baseModel, OntModel otherModel) { public static OntModel createUnion(OntModel baseModel, OntModel plusModel) {
@SuppressWarnings("deprecation") Graph baseGraph = baseModel.getGraph();
BulkUpdateHandler buh = baseModel.getGraph().getBulkUpdateHandler(); Graph plusGraph = plusModel.getGraph();
BulkUpdatingUnion unionGraph = new BulkUpdatingUnion(baseGraph,
plusGraph);
Model unionModel = createUnion((Model) baseModel, (Model) otherModel); Model unionModel = ModelFactory.createModelForGraph(unionGraph);
OntModel unionOntModel = ModelFactory.createOntologyModel(OWL_MEM, OntModel unionOntModel = ModelFactory.createOntologyModel(OWL_MEM,
unionModel); unionModel);
return new BulkUpdatingOntModel(unionOntModel, buh); return new BulkUpdatingOntModel(unionOntModel);
} }
public static Model createModelForGraph(Graph g) { public static Model createModelForGraph(Graph g) {
@SuppressWarnings("deprecation") BulkUpdateHandler buh = getBulkUpdateHandler(g);
BulkUpdateHandler buh = g.getBulkUpdateHandler();
return new BulkUpdatingModel(ModelFactory.createModelForGraph(g), buh); return new BulkUpdatingModel(ModelFactory.createModelForGraph(g), buh);
} }
private static class BulkUpdatingUnion extends Union {
@SuppressWarnings("deprecation")
public BulkUpdatingUnion(Graph L, Graph R) {
super(L, R);
this.bulkHandler = new WrappedBulkUpdateHandler(this,
L.getBulkUpdateHandler());
}
}
@SuppressWarnings("deprecation")
private static BulkUpdateHandler getBulkUpdateHandler(Graph graph) {
return graph.getBulkUpdateHandler();
}
} }

View file

@ -0,0 +1,116 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.testing;
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.Collections;
import java.util.List;
/**
* The create() method creates a dynamic Proxy that wraps your inner object, and
* implements your interfaze.
*
* It also implements the MethodCallRecorder interface (although you will need
* to cast it), so you can find out what methods were called on the proxy.
*/
public class RecordingProxy {
public static <T> T create(T inner, Class<T> interfaze) {
RecordingInvocationHandler handler = new RecordingInvocationHandler(
inner);
ClassLoader classLoader = interfaze.getClassLoader();
Class<?>[] interfaces = new Class<?>[] { interfaze,
MethodCallRecorder.class };
return interfaze.cast(Proxy.newProxyInstance(classLoader, interfaces,
handler));
}
/**
* The "add-on" interface that allows us to ask what methods were called on
* the proxy since it was created, or since it was reset.
*/
public interface MethodCallRecorder {
List<MethodCall> getMethodCalls();
List<String> getMethodCallNames();
void resetMethodCalls();
}
public static class MethodCall {
/** a convenience method to get just the names of the methods called. */
public static Object justNames(List<MethodCall> methodCalls) {
List<String> names = new ArrayList<>();
for (MethodCall methodCall : methodCalls) {
names.add(methodCall.getName());
}
return names;
}
private final String name;
private final List<Object> argList;
public MethodCall(String name, Object[] args) {
this.name = name;
if (args == null) {
this.argList = Collections.emptyList();
} else {
this.argList = Collections.unmodifiableList(new ArrayList<>(
Arrays.asList(args)));
}
}
public String getName() {
return name;
}
public List<Object> getArgList() {
return argList;
}
}
public static class RecordingInvocationHandler implements InvocationHandler {
private final Object inner;
private final List<MethodCall> methodCalls = new ArrayList<>();
RecordingInvocationHandler(Object inner) {
this.inner = inner;
}
List<MethodCall> getMethodCalls() {
return new ArrayList<>(methodCalls);
}
void reset() {
methodCalls.clear();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
switch (method.getName()) {
case "getMethodCalls":
return new ArrayList<MethodCall>(methodCalls);
case "getMethodCallNames":
return MethodCall.justNames(methodCalls);
case "resetMethodCalls":
methodCalls.clear();
return null;
case "equals":
if (args == null) return false;
if (args.length == 0) return false;
return args[0].equals(inner);
default:
methodCalls.add(new MethodCall(method.getName(), args));
return method.invoke(inner, args);
}
}
}
}

View file

@ -0,0 +1,722 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.rdfservice.adapters;
import static com.hp.hpl.jena.ontology.OntModelSpec.OWL_MEM;
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import com.hp.hpl.jena.graph.BulkUpdateHandler;
import com.hp.hpl.jena.graph.impl.GraphWithPerform;
import com.hp.hpl.jena.graph.impl.SimpleBulkUpdateHandler;
import com.hp.hpl.jena.mem.GraphMem;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelChangedListener;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
import edu.cornell.mannlib.vitro.testing.RecordingProxy;
import edu.cornell.mannlib.vitro.testing.RecordingProxy.MethodCall;
import edu.cornell.mannlib.vitro.testing.RecordingProxy.MethodCallRecorder;
/**
* Test that the VitroModelFactory is doing what we want, with regard to bulk
* updates.
*
* With the switch to Jena 2.10, bulk update operations are deprecated, but
* still supported, to a large extent. A Graph still has a bulk updater which
* can be called for bulk operations (like adding multiple statements). However,
* the default Model won't call the bulk updater of its Graph, and neither will
* the default OntModel.
*
* VitroModelFactory creates Models and OntModels that do call the bulk updaters
* of their respective graphs.
*
* ---------------
*
* These tests show which methods are called on which objects (graph, model,
* listener) for both simple operations (add a statement) and bulk operations
* (add multiple statements).
*
* The tests of the default ModelFactory aren't necessary. They do add
* confidence to the testing mechanism, and provide a contrast with the
* VitroModelFactory.
*
* The tests of simple operations may or may not add value. Probably good to
* keep them.
*
* ----------------
*
* Who knows how we will deal with this in the next Jena upgrade, when
* presumably the bulk updaters will be removed completely.
*/
public class VitroModelFactoryTest extends AbstractTestClass {
private static final Statement SINGLE_STATEMENT = stmt(
resource("http://subject"), property("http://add"),
literal("object"));
private static final Statement[] MULTIPLE_STATEMENTS = {
stmt(resource("http://subject"), property("http://add"),
literal("first")),
stmt(resource("http://subject"), property("http://add"),
literal("second")) };
private static final String[] BORING_METHOD_NAMES = { "getPrefixMapping",
"getEventManager", "getBulkUpdateHandler", "find", "getGraph" };
// ----------------------------------------------------------------------
// createModelForGraph()
// ----------------------------------------------------------------------
/**
* A ModelGroup has a talkative graph, with a talkative bulkUpdater, wrapped
* by a model that has a talkative listener attached.
*
* But what kind of model?
*/
private static abstract class ModelGroup extends TestObjectGrouping {
final GraphWithPerform g;
final BulkUpdateHandler bu;
final ModelChangedListener l;
final Model m;
protected ModelGroup() {
MyGraphMem rawGraph = new MyGraphMem();
this.g = wrapGraph(rawGraph);
this.bu = makeBulkUpdater(this.g, rawGraph);
this.l = makeListener();
this.m = wrapModel(makeModel(this.g));
this.m.register(this.l);
reset(g);
reset(bu);
reset(l);
reset(m);
}
protected abstract Model makeModel(GraphWithPerform g);
}
/** A ModelGroup with a default-style model. */
private static class DefaultModelGroup extends ModelGroup {
@Override
protected Model makeModel(GraphWithPerform g) {
return ModelFactory.createModelForGraph(g);
}
}
/** A ModelGroup with a Vitro-style model. */
private static class VitroModelGroup extends ModelGroup {
@Override
protected Model makeModel(GraphWithPerform g) {
return VitroModelFactory.createModelForGraph(g);
}
}
private ModelGroup mg;
@Test
public void addOneToModel() {
mg = new DefaultModelGroup();
mg.m.add(SINGLE_STATEMENT);
new MethodCalls().add(mg.g, "add").add(mg.bu)
.add(mg.l, "addedStatement").test();
}
@Test
public void addOneToVitroModel() {
mg = new VitroModelGroup();
mg.m.add(SINGLE_STATEMENT);
new MethodCalls().add(mg.g, "add").add(mg.bu)
.add(mg.l, "addedStatement").test();
}
@Test
public void addMultipleToModel() {
mg = new DefaultModelGroup();
mg.m.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(mg.g, "performAdd", "performAdd").add(mg.bu)
.add(mg.l, "addedStatements").test();
}
@Test
public void addMultipleToVitroModel() {
mg = new VitroModelGroup();
mg.m.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(mg.g, "performAdd", "performAdd")
.add(mg.bu, "add").add(mg.l, "addedStatements").test();
}
// ----------------------------------------------------------------------
// createOntologyModel()
// ----------------------------------------------------------------------
private OntModelGroup omg;
/**
* An OntModelGroup is like a ModelGroup, but the model is wrapped in an
* OntModel that has its own talkative listener.
*
* But what kind of Model, and what kind of OntModel?
*/
private static abstract class OntModelGroup extends ModelGroup {
final ModelChangedListener ol;
final OntModel om;
protected OntModelGroup() {
this.ol = makeListener();
this.om = wrapOntModel(makeOntModel(this.m));
this.om.register(this.ol);
}
protected abstract OntModel makeOntModel(Model m);
}
/**
* An OntModelGroup with a default-style OntModel and a default-style Model.
*/
private static class DefaultOntModelGroup extends OntModelGroup {
@Override
protected OntModel makeOntModel(Model m) {
return ModelFactory.createOntologyModel(OWL_MEM, m);
}
@Override
protected Model makeModel(GraphWithPerform g) {
return ModelFactory.createModelForGraph(g);
}
}
/**
* An OntModelGroup with a Vitro-style OntModel and a Vitro-style Model.
*/
private static class VitroOntModelGroup extends OntModelGroup {
@Override
protected OntModel makeOntModel(Model m) {
return VitroModelFactory.createOntologyModel(m);
}
@Override
protected Model makeModel(GraphWithPerform g) {
return VitroModelFactory.createModelForGraph(g);
}
}
@Test
public void addOneToOntModel() {
omg = new DefaultOntModelGroup();
omg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(omg.g, "add").add(omg.bu)
.add(omg.l, "addedStatement").add(omg.ol, "addedStatement")
.test();
}
@Test
public void addOneToVitroOntModel() {
omg = new VitroOntModelGroup();
omg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(omg.g, "add").add(omg.bu)
.add(omg.l, "addedStatement").add(omg.ol, "addedStatement")
.test();
}
@Test
public void addMultipleToOntModel() {
omg = new DefaultOntModelGroup();
omg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(omg.g, "add", "add").add(omg.bu)
.add(omg.l, "addedStatement", "addedStatement")
.add(omg.ol, "addedStatements").test();
}
@Test
public void addMultipleToVitroOntModel() {
omg = new VitroOntModelGroup();
omg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(omg.g, "performAdd", "performAdd")
.add(omg.bu, "add").add(omg.l, "addedStatements")
.add(omg.ol, "addedStatements").test();
}
// ----------------------------------------------------------------------
// createUnion(Model, Model)
// ----------------------------------------------------------------------
/**
* A UnionModelGroup is two ModelGroups, joined into a union that has its
* own talkative listener.
*
* But what kind of ModelGroup, and what kind of union?
*/
private abstract static class UnionModelGroup extends TestObjectGrouping {
final ModelGroup base;
final ModelGroup plus;
final Model m;
final ModelChangedListener l;
protected UnionModelGroup() {
this.base = makeModelGroup();
this.plus = makeModelGroup();
this.m = wrapModel(makeUnion(this.base.m, this.plus.m));
this.l = makeListener();
this.m.register(this.l);
}
protected abstract ModelGroup makeModelGroup();
protected abstract Model makeUnion(Model baseModel, Model plusModel);
}
/**
* A UnionModelGroup with default-style Models and a default-style union.
*/
private static class DefaultUnionModelGroup extends UnionModelGroup {
@Override
protected ModelGroup makeModelGroup() {
return new DefaultModelGroup();
}
@Override
protected Model makeUnion(Model baseModel, Model plusModel) {
return ModelFactory.createUnion(baseModel, plusModel);
}
}
/**
* A UnionModelGroup with Vitro-style Models and a Vitro-style union.
*/
private static class VitroUnionModelGroup extends UnionModelGroup {
@Override
protected ModelGroup makeModelGroup() {
return new VitroModelGroup();
}
@Override
protected Model makeUnion(Model baseModel, Model plusModel) {
return VitroModelFactory.createUnion(baseModel, plusModel);
}
}
private UnionModelGroup umg;
@Test
public void addOneToUnion() {
umg = new DefaultUnionModelGroup();
umg.m.add(SINGLE_STATEMENT);
new MethodCalls().add(umg.base.g, "add").add(umg.base.bu)
.add(umg.base.l, "addedStatement").add(umg.plus.g)
.add(umg.plus.bu).add(umg.plus.l).add(umg.l, "addedStatement")
.test();
}
@Test
public void addOneToVitroUnion() {
umg = new VitroUnionModelGroup();
umg.m.add(SINGLE_STATEMENT);
new MethodCalls().add(umg.base.g, "add").add(umg.base.bu)
.add(umg.base.l, "addedStatement").add(umg.plus.g)
.add(umg.plus.bu).add(umg.plus.l).add(umg.l, "addedStatement")
.test();
}
@Test
public void addMultipleToUnion() {
umg = new DefaultUnionModelGroup();
umg.m.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(umg.base.g, "add", "add").add(umg.base.bu)
.add(umg.base.l, "addedStatement", "addedStatement")
.add(umg.plus.g).add(umg.plus.bu).add(umg.plus.l)
.add(umg.l, "addedStatements").test();
}
@Test
public void addMultipleToVitroUnion() {
umg = new VitroUnionModelGroup();
umg.m.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(umg.base.g, "performAdd", "performAdd")
.add(umg.base.bu, "add").add(umg.base.l, "addedStatements")
.add(umg.plus.g).add(umg.plus.bu).add(umg.plus.l)
.add(umg.l, "addedStatements").test();
}
// ----------------------------------------------------------------------
// createUnion(OntModel, OntModel)
// ----------------------------------------------------------------------
/**
* A UnionOntModelGroup is two OntModelGroups, joined into a union that has
* its own talkative listener.
*
* But what kind of OntModelGroup, and what kind of union?
*/
private abstract static class UnionOntModelGroup extends TestObjectGrouping {
final OntModelGroup base;
final OntModelGroup plus;
final OntModel om;
final ModelChangedListener l;
protected UnionOntModelGroup() {
this.base = makeOntModelGroup();
this.plus = makeOntModelGroup();
this.om = wrapOntModel(makeOntUnion(this.base.om, this.plus.om));
this.l = makeListener();
this.om.register(this.l);
}
protected abstract OntModelGroup makeOntModelGroup();
protected abstract OntModel makeOntUnion(OntModel baseModel,
OntModel plusModel);
}
/**
* A UnionOntModelGroup with default-style OntModels and a default-style
* union.
*/
private static class DefaultUnionOntModelGroup extends UnionOntModelGroup {
@Override
protected OntModelGroup makeOntModelGroup() {
return new DefaultOntModelGroup();
}
@Override
protected OntModel makeOntUnion(OntModel baseModel, OntModel plusModel) {
return ModelFactory.createOntologyModel(OWL_MEM,
ModelFactory.createUnion(baseModel, plusModel));
}
}
/**
* A UnionOntModelGroup with Vitro-style OntModels and a Vitro-style union.
*/
private static class VitroUnionOntModelGroup extends UnionOntModelGroup {
@Override
protected OntModelGroup makeOntModelGroup() {
return new VitroOntModelGroup();
}
@Override
protected OntModel makeOntUnion(OntModel baseModel, OntModel plusModel) {
return VitroModelFactory.createUnion(baseModel, plusModel);
}
}
private UnionOntModelGroup uomg;
@Test
public void addOneToOntUnion() {
uomg = new DefaultUnionOntModelGroup();
uomg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(uomg.base.g, "add").add(uomg.base.bu)
.add(uomg.base.l, "addedStatement").add(uomg.plus.g)
.add(uomg.plus.bu).add(uomg.plus.l)
.add(uomg.l, "addedStatement").test();
}
@Test
public void addOneToVitroOntUnion() {
uomg = new VitroUnionOntModelGroup();
uomg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(uomg.base.g, "add").add(uomg.base.bu)
.add(uomg.base.l, "addedStatement").add(uomg.plus.g)
.add(uomg.plus.bu).add(uomg.plus.l)
.add(uomg.l, "addedStatement").test();
}
@Test
public void addMultipleToOntUnion() {
uomg = new DefaultUnionOntModelGroup();
uomg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(uomg.base.g, "add", "add").add(uomg.base.bu)
.add(uomg.base.l, "addedStatement", "addedStatement")
.add(uomg.plus.g).add(uomg.plus.bu).add(uomg.plus.l)
.add(uomg.l, "addedStatements").test();
}
@Test
public void addMultipleToVitroOntUnion() {
uomg = new VitroUnionOntModelGroup();
uomg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(uomg.base.g, "performAdd", "performAdd")
.add(uomg.base.bu, "add").add(uomg.base.l, "addedStatements")
.add(uomg.plus.g).add(uomg.plus.bu).add(uomg.plus.l)
.add(uomg.l, "addedStatements").test();
}
// ----------------------------------------------------------------------
// OntModel of Union of Models
//
// This shouldn't hold any surprises, should it?
// ----------------------------------------------------------------------
/**
* A OntModelUnionModelGroup is a UnionModelGroup wrapped by an OntModel
* with a listener.
*
* But what kind of UnionModelGroup, and what kind of OntModel?
*/
private abstract static class OntModelUnionModelGroup extends
TestObjectGrouping {
final UnionModelGroup union;
final OntModel om;
final ModelChangedListener ol;
protected OntModelUnionModelGroup() {
this.union = makeUnionModelGroup();
this.om = wrapOntModel(makeOntModel(union.m));
this.ol = makeListener();
this.om.register(this.ol);
reset(om);
}
protected abstract UnionModelGroup makeUnionModelGroup();
protected abstract OntModel makeOntModel(Model m);
}
/**
* A OntModelUnionModelGroup with default-style UnionModelGroup and a
* default-style OntModel.
*/
private static class DefaultOntModelUnionModelGroup extends
OntModelUnionModelGroup {
@Override
protected UnionModelGroup makeUnionModelGroup() {
return new DefaultUnionModelGroup();
}
@Override
protected OntModel makeOntModel(Model m) {
return ModelFactory.createOntologyModel(OWL_MEM, m);
}
}
/**
* A OntModelUnionModelGroup with Vitro-style UnionModelGroup and a
* Vitro-style OntModel.
*/
private static class VitroOntModelUnionModelGroup extends
OntModelUnionModelGroup {
@Override
protected UnionModelGroup makeUnionModelGroup() {
return new VitroUnionModelGroup();
}
@Override
protected OntModel makeOntModel(Model m) {
return VitroModelFactory.createOntologyModel(m);
}
}
private OntModelUnionModelGroup omumg;
@Test
public void addOneToOntModeledUnionModel() {
omumg = new DefaultOntModelUnionModelGroup();
omumg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(omumg.om, "add").add(omumg.ol, "addedStatement")
.add(omumg.union.base.g, "add").add(omumg.union.base.bu)
.add(omumg.union.base.m)
.add(omumg.union.base.l, "addedStatement")
.add(omumg.union.plus.g).add(omumg.union.plus.bu)
.add(omumg.union.plus.m).add(omumg.union.plus.l).test();
}
@Test
public void addOneToVitroOntModeledUnionModel() {
omumg = new VitroOntModelUnionModelGroup();
omumg.om.add(SINGLE_STATEMENT);
new MethodCalls().add(omumg.om, "add").add(omumg.ol, "addedStatement")
.add(omumg.union.base.g, "add").add(omumg.union.base.bu)
.add(omumg.union.base.m)
.add(omumg.union.base.l, "addedStatement")
.add(omumg.union.plus.g).add(omumg.union.plus.bu)
.add(omumg.union.plus.m).add(omumg.union.plus.l).test();
}
@Test
public void addMultipleToOntModeledUnionModel() {
omumg = new DefaultOntModelUnionModelGroup();
omumg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(omumg.om, "add").add(omumg.ol, "addedStatements")
.add(omumg.union.base.g, "add", "add").add(omumg.union.base.bu)
.add(omumg.union.base.m)
.add(omumg.union.base.l, "addedStatement", "addedStatement")
.add(omumg.union.plus.g).add(omumg.union.plus.bu)
.add(omumg.union.plus.m).add(omumg.union.plus.l).test();
}
@Test
public void addMultipleToVitroOntModeledUnionModel() {
omumg = new VitroOntModelUnionModelGroup();
omumg.om.add(MULTIPLE_STATEMENTS);
new MethodCalls().add(omumg.om, "add").add(omumg.ol, "addedStatements")
.add(omumg.union.base.g, "performAdd", "performAdd")
.add(omumg.union.base.bu, "add").add(omumg.union.base.m)
.add(omumg.union.base.l, "addedStatements")
.add(omumg.union.plus.g).add(omumg.union.plus.bu)
.add(omumg.union.plus.m).add(omumg.union.plus.l).test();
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
private static Statement stmt(Resource subject, Property predicate,
RDFNode object) {
return ResourceFactory.createStatement(subject, predicate, object);
}
private static Resource resource(String uri) {
return ResourceFactory.createResource(uri);
}
private static Property property(String uri) {
return ResourceFactory.createProperty(uri);
}
private static Literal literal(String value) {
return ResourceFactory.createPlainLiteral(value);
}
/** Just for debugging */
private void dumpMethodCalls(String message, Object proxy) {
System.out.println(message + " method calls:");
for (MethodCall call : ((MethodCallRecorder) proxy).getMethodCalls()) {
String formatted = " " + call.getName();
for (Object arg : call.getArgList()) {
formatted += " " + arg.getClass();
}
System.out.println(formatted);
}
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
/**
* The latest Graph classes allow you to get their BulkUpdateHandler, but
* won't allow you to set it.
*/
private static class MyGraphMem extends GraphMem {
public void setBulkUpdateHandler(BulkUpdateHandler bulkHandler) {
this.bulkHandler = bulkHandler;
}
}
/**
* A collection of "CallNames", each of which holds a list of expected
* calls, a recording proxy from which we can get the actual calls, and a
* method to compare them.
*/
private static class MethodCalls {
private final List<CallNames> list = new ArrayList<>();
public MethodCalls add(Object proxy, String... names) {
list.add(new CallNames((MethodCallRecorder) proxy, names));
return this;
}
/**
* Create a string that represents all of the expected method calls.
* Create a string that represents all of the interesting actual calls.
* Compare the strings.
*/
private void test() {
try (StringWriter expectSw = new StringWriter();
PrintWriter expectWriter = new PrintWriter(expectSw, true);
StringWriter actualSw = new StringWriter();
PrintWriter actualWriter = new PrintWriter(actualSw, true);) {
for (CallNames calls : list) {
expectWriter.println(Arrays.asList(calls.names));
actualWriter.println(filterMethodNames(calls.proxy
.getMethodCallNames()));
}
assertEquals(expectSw.toString(), actualSw.toString());
} catch (IOException e) {
fail(e.toString());
}
}
private List<String> filterMethodNames(List<String> raw) {
List<String> filtered = new ArrayList<>(raw);
filtered.removeAll(Arrays.asList(BORING_METHOD_NAMES));
return filtered;
}
private static class CallNames {
private final MethodCallRecorder proxy;
private final String[] names;
public CallNames(MethodCallRecorder proxy, String[] names) {
this.proxy = proxy;
this.names = names;
}
}
}
/**
* Some utility methods for creating a group of test objects.
*/
private static abstract class TestObjectGrouping {
protected GraphWithPerform wrapGraph(MyGraphMem raw) {
return RecordingProxy.create(raw, GraphWithPerform.class);
}
protected BulkUpdateHandler makeBulkUpdater(GraphWithPerform g,
MyGraphMem raw) {
SimpleBulkUpdateHandler rawBu = new SimpleBulkUpdateHandler(g);
BulkUpdateHandler bu = RecordingProxy.create(rawBu,
BulkUpdateHandler.class);
raw.setBulkUpdateHandler(bu);
return bu;
}
protected static ModelChangedListener makeListener() {
return RecordingProxy.create(new StatementListener(),
ModelChangedListener.class);
}
protected Model wrapModel(Model m) {
return RecordingProxy.create(m, Model.class);
}
protected OntModel wrapOntModel(OntModel om) {
return RecordingProxy.create(om, OntModel.class);
}
protected <T> T reset(T proxy) {
((MethodCallRecorder) proxy).resetMethodCalls();
return proxy;
}
}
}