Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trillian updates #46

Merged
merged 49 commits into from
Oct 7, 2022
Merged
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
29dd88c
Allow predicates and objects to be Java objects. Store IRIs correctly…
twalmsley Jul 17, 2022
d4720d4
Fix problem with spaces at the end of an IRI String
twalmsley Jul 21, 2022
c72567e
Checkpoint
twalmsley Jul 17, 2022
fd76e98
Update after MC bug fix.
twalmsley Jul 18, 2022
72b6b67
Remove debugging code
twalmsley Jul 18, 2022
eaad266
Improved unit tests
twalmsley Jul 18, 2022
6273b95
Fix bug in Predicates.java
twalmsley Jul 22, 2022
5d4ce72
Add method for finding participant details in associations between tw…
twalmsley Jul 19, 2022
f3c724b
Add method for finding participant details in associations between tw…
twalmsley Jul 19, 2022
e992071
Fix DynamicObjects and HqdmObject.equals
twalmsley Jul 19, 2022
92215c0
Fix HqdmObject.hashCode because it wasn't working for HashSets
twalmsley Jul 19, 2022
877f562
Add unit test for findParticipantDetails and fix the code to match.
twalmsley Jul 19, 2022
a93f541
Add unit test for findParticipantDetails and fix the code to match.
twalmsley Jul 19, 2022
031c670
Fix incorrect use of KIND_OF_ASSOCIATION
twalmsley Jul 20, 2022
ba300bf
Exposed a method on MagmaCoreServices to export a dataset as TTL.
twalmsley Jul 21, 2022
550dedb
MagmaCoreRemoteSparqlDatabase does not support READ_PROMOTE transacti…
twalmsley Jul 21, 2022
48725df
Bulk deletes and creates.
twalmsley Jul 29, 2022
d975f09
Bulk deletes and creates.
twalmsley Jul 29, 2022
79baac2
Allow predicates and objects to be Java objects. Store IRIs correctly…
twalmsley Jul 17, 2022
9c63c72
Use SPARQL for FindParticipantDetails
twalmsley Aug 1, 2022
e5d73b0
Improve FindBySign performance.
twalmsley Aug 2, 2022
2bcd080
Separate SPARQL queries from MagmaCoreService so they're not exported…
twalmsley Aug 2, 2022
171d52d
Add service findByTypeKindAndSign and unit test.
twalmsley Aug 3, 2022
203bf10
Add service findByTypeKindAndSign and unit test.
twalmsley Aug 3, 2022
b62d35a
Add FindByKindOfAssociation.
twalmsley Aug 4, 2022
bc10267
Add FindByKindOfAssociation.
twalmsley Aug 4, 2022
b57869b
Added a (failing) unit test for FindByKindOfAssociation - needs bette…
twalmsley Aug 5, 2022
56907b4
Added FindByKindOfAssociation, and DataIntegriryReport
twalmsley Aug 8, 2022
cb63446
Simplify an overcomplicated query.
twalmsley Aug 8, 2022
4430d28
Fix a problem with Data Integrity Checks against remote SPARQL endpoints
twalmsley Aug 10, 2022
dd1c41f
Add a new data integrity check
twalmsley Aug 10, 2022
7ec2627
Fix a data integrity query
twalmsley Aug 11, 2022
a9c4295
Added two new queries to support application clients.
twalmsley Aug 23, 2022
db9e292
Added a missing line from a SPARQL query.
twalmsley Aug 23, 2022
9d330a0
Fix a SPARQL query with bad use of OPTIONAL parts.
twalmsley Sep 2, 2022
b101199
Added some search queries to find entities with various parameters.
twalmsley Sep 5, 2022
0f539e7
Added some extra finder methods.
twalmsley Sep 7, 2022
c0282e2
Undeprecate ENTITY_NAME.
twalmsley Sep 9, 2022
800f436
Remove filter that wasn't in use.
twalmsley Sep 9, 2022
e559d5a
Added find by field value and class.
twalmsley Sep 14, 2022
8a7ba3a
Use Instant instead of LocalDateTime to prevent GMT/BST errors.
twalmsley Sep 15, 2022
4a0e81f
Added a data import feature. Useful for DB delta management.
twalmsley Sep 20, 2022
72d2b07
Merge pull request #42 from twalmsley/aosd-multiple-issue-fixes
GCHQDeveloper42 Sep 23, 2022
66e2071
Fixed Javadocs and formatting issues.
GCHQDeveloper42 Oct 3, 2022
38130b1
Removed ENTITY_ID from HQDM RDF definitions.
GCHQDeveloper42 Oct 3, 2022
4510f47
Fixed typo in DataIntegrityReport.java: CHECK_STATE_OF_SIGN_PRTICIPAN…
GCHQDeveloper42 Oct 3, 2022
4c67f7a
Renamed McAssistMultInheritFromDataApp.java > McAssistMultiInheritFro…
GCHQDeveloper42 Oct 3, 2022
b18c945
Remove unused Predicates class and its test.
GCHQDeveloper42 Oct 3, 2022
753fddb
Merge branch 'develop' into trillian-updates
GCHQDeveloper42 Oct 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Allow predicates and objects to be Java objects. Store IRIs correctly…
… in the databases.
twalmsley committed Jul 23, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 29dd88cca519d4192870b75c1375b80c0a581728
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
@@ -165,10 +166,19 @@ public Thing get(final IRI iri) {
*/
@Override
public void create(final Thing object) {
final Resource resource = dataset.getDefaultModel().createResource(object.getId());

object.getPredicates().forEach((iri, predicates) -> predicates.forEach(predicate -> resource
.addProperty(dataset.getDefaultModel().createProperty(iri.toString()), predicate.toString())));
final Model defaultModel = dataset.getDefaultModel();

final Resource resource = defaultModel.createResource(object.getId());

object.getPredicates()
.forEach((iri, predicates) -> predicates.forEach(value -> {
if (value instanceof IRI) {
final Resource valueResource = defaultModel.createResource(value.toString());
resource.addProperty(defaultModel.createProperty(iri.toString()), valueResource);
} else {
resource.addProperty(defaultModel.createProperty(iri.toString()), value.toString());
}
}));
}

/**
@@ -283,27 +293,38 @@ private final QueryResultList getQueryResultList(final QueryExecution queryExec)
}

private final List<Thing> toTopObjects(final QueryResultList queryResultsList) {
final Map<String, List<Pair<String, String>>> objectMap = new HashMap<>();
final String subjectVarName = ((List<String>) queryResultsList.getVarNames()).get(0);
final String predicateVarName = ((List<String>) queryResultsList.getVarNames()).get(1);
final String objectVarName = ((List<String>) queryResultsList.getVarNames()).get(2);
final Map<RDFNode, List<Pair<Object, Object>>> objectMap = new HashMap<>();
final List<String> varNames = (List<String>) queryResultsList.getVarNames();

final String subjectVarName = varNames.get(0);
final String predicateVarName = varNames.get(1);
final String objectVarName = varNames.get(2);

// Create a map of the triples for each unique subject IRI
final List<QueryResult> queryResults = queryResultsList.getQueryResults();
queryResults.forEach(queryResult -> {
final String subjectValue = queryResult.get(subjectVarName).toString();
final String predicateValue = queryResult.get(predicateVarName).toString();
final String objectValue = queryResult.get(objectVarName).toString();
final RDFNode subjectValue = queryResult.get(subjectVarName);
final RDFNode predicateValue = queryResult.get(predicateVarName);
final RDFNode objectValue = queryResult.get(objectVarName);

List<Pair<String, String>> dataModelObject = objectMap.get(subjectValue);
List<Pair<Object, Object>> dataModelObject = objectMap.get(subjectValue);
if (dataModelObject == null) {
dataModelObject = new ArrayList<>();
objectMap.put(subjectValue, dataModelObject);
}
dataModelObject.add(new Pair<>(predicateValue, objectValue));
if (objectValue instanceof Literal) {
dataModelObject.add(new Pair<>(new IRI(predicateValue.toString()), objectValue.toString()));
} else if (objectValue instanceof Resource) {
dataModelObject.add(new Pair<>(new IRI(predicateValue.toString()), new IRI(objectValue.toString())));
} else {
throw new RuntimeException("objectValue is of unknown type: " + objectValue.getClass());
}
});

return objectMap.entrySet().stream().map(entry -> HqdmObjectFactory.create(entry.getKey(), entry.getValue()))
return objectMap
.entrySet()
.stream()
.map(entry -> HqdmObjectFactory.create(new IRI(entry.getKey().toString()), entry.getValue()))
.collect(Collectors.toList());
}

Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.RDFNode;
@@ -140,9 +141,15 @@ public void create(final Thing object) {

final Resource resource = model.createResource(object.getId());

object.getPredicates().forEach((iri, predicates) -> predicates.forEach(
predicate -> resource.addProperty(model.createProperty(iri.toString()), predicate.toString())));

object.getPredicates()
.forEach((iri, predicates) -> predicates.forEach(value -> {
if (value instanceof IRI) {
final Resource valueResource = model.createResource(value.toString());
resource.addProperty(model.createProperty(iri.toString()), valueResource);
} else {
resource.addProperty(model.createProperty(iri.toString()), value.toString());
}
}));
connection.load(model);
}

@@ -257,27 +264,38 @@ private final QueryResultList getQueryResultList(final QueryExecution queryExec)
}

private final List<Thing> toTopObjects(final QueryResultList queryResultsList) {
final Map<String, List<Pair<String, String>>> objectMap = new HashMap<>();
final String subjectVarName = queryResultsList.getVarNames().get(0);
final String predicateVarName = queryResultsList.getVarNames().get(1);
final String objectVarName = queryResultsList.getVarNames().get(2);
final Map<RDFNode, List<Pair<Object, Object>>> objectMap = new HashMap<>();
final List<String> varNames = (List<String>) queryResultsList.getVarNames();

final String subjectVarName = varNames.get(0);
final String predicateVarName = varNames.get(1);
final String objectVarName = varNames.get(2);

// Create a map of the triples for each unique subject IRI.
// Create a map of the triples for each unique subject IRI
final List<QueryResult> queryResults = queryResultsList.getQueryResults();
queryResults.forEach(queryResult -> {
final String subjectValue = queryResult.get(subjectVarName).toString();
final String predicateValue = queryResult.get(predicateVarName).toString();
final String objectValue = queryResult.get(objectVarName).toString();
final RDFNode subjectValue = queryResult.get(subjectVarName);
final RDFNode predicateValue = queryResult.get(predicateVarName);
final RDFNode objectValue = queryResult.get(objectVarName);

List<Pair<String, String>> dataModelObject = objectMap.get(subjectValue);
List<Pair<Object, Object>> dataModelObject = objectMap.get(subjectValue);
if (dataModelObject == null) {
dataModelObject = new ArrayList<>();
objectMap.put(subjectValue, dataModelObject);
}
dataModelObject.add(new Pair<>(predicateValue, objectValue));
if (objectValue instanceof Literal) {
dataModelObject.add(new Pair<>(new IRI(predicateValue.toString()), objectValue.toString()));
} else if (objectValue instanceof Resource) {
dataModelObject.add(new Pair<>(new IRI(predicateValue.toString()), new IRI(objectValue.toString())));
} else {
throw new RuntimeException("objectValue is of unknown type: " + objectValue.getClass());
}
});

return objectMap.entrySet().stream().map(entry -> HqdmObjectFactory.create(entry.getKey(), entry.getValue()))
return objectMap
.entrySet()
.stream()
.map(entry -> HqdmObjectFactory.create(new IRI(entry.getKey().toString()), entry.getValue()))
.collect(Collectors.toList());
}

Original file line number Diff line number Diff line change
@@ -34,16 +34,16 @@ public class DbCreateOperation implements Function<MagmaCoreService, MagmaCoreSe
private IRI predicate;

// The value of the property we're referring to.
private String object;
private Object object;

/**
* Constructs a DbCreateOperation to create a predicate.
*
* @param subject Subject {@link IRI}.
* @param predicate Predicate {@link IRI}.
* @param object {@link String} value.
* @param object {@link Object} value.
*/
public DbCreateOperation(final IRI subject, final IRI predicate, final String object) {
public DbCreateOperation(final IRI subject, final IRI predicate, final Object object) {
this.subject = subject;
this.predicate = predicate;
this.object = object;
@@ -58,11 +58,11 @@ public MagmaCoreService apply(final MagmaCoreService mcService) {

if (thing == null) {
final Thing newThing = SpatioTemporalExtentServices.createThing(subject.getIri());
newThing.addStringValue(predicate.getIri(), object);
newThing.addValue(predicate, object);
mcService.create(newThing);
} else {
if (!thing.hasThisValue(predicate.getIri(), object)) {
thing.addValue(predicate.getIri(), object);
if (!thing.hasThisValue(predicate, object)) {
thing.addValue(predicate, object);
mcService.update(thing);
} else {
throw new DbTransformationException(
Original file line number Diff line number Diff line change
@@ -27,16 +27,16 @@
public class DbDeleteOperation implements Function<MagmaCoreService, MagmaCoreService> {
private IRI subject;
private IRI predicate;
private String object;
private Object object;

/**
* Constructs a DbDeleteOperation to delete a predicate.
*
* @param subject Subject {@link IRI}.
* @param predicate Predicate {@link IRI}.
* @param object {@link String} value.
* @param object {@link Object} value.
*/
public DbDeleteOperation(final IRI subject, final IRI predicate, final String object) {
public DbDeleteOperation(final IRI subject, final IRI predicate, final Object object) {
this.subject = subject;
this.predicate = predicate;
this.object = object;
@@ -48,8 +48,8 @@ public DbDeleteOperation(final IRI subject, final IRI predicate, final String ob
public MagmaCoreService apply(final MagmaCoreService mcService) {
final Thing thing = mcService.get(subject);

if (thing != null && thing.hasThisValue(predicate.getIri(), object)) {
thing.removeValue(predicate.getIri(), object);
if (thing != null && thing.hasThisValue(predicate, object)) {
thing.removeValue(predicate, object);
mcService.update(thing);
return mcService;
}
Original file line number Diff line number Diff line change
@@ -46,10 +46,13 @@ public void testApplyAndInvert() {

// Create operations to add an object with dummy values.
final IRI individualIri = new IRI(TEST_BASE, "individual");
final IRI classOfIndividualIri = new IRI(TEST_BASE, "classOfIndividual");
final IRI possibleWorldIri = new IRI(TEST_BASE, "possible_world");

final DbChangeSet createIndividual = new DbChangeSet(List.of(),
List.of(new DbCreateOperation(individualIri, RDFS.RDF_TYPE, HQDM.INDIVIDUAL.getIri()),
new DbCreateOperation(individualIri, HQDM.MEMBER_OF, "classOfIndividual"),
new DbCreateOperation(individualIri, HQDM.PART_OF_POSSIBLE_WORLD, "possible world")));
List.of(new DbCreateOperation(individualIri, RDFS.RDF_TYPE, HQDM.INDIVIDUAL),
new DbCreateOperation(individualIri, HQDM.MEMBER_OF, classOfIndividualIri),
new DbCreateOperation(individualIri, HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri)));

// Apply the operations to the dataset.
mcService.runInTransaction(createIndividual);
@@ -58,9 +61,9 @@ public void testApplyAndInvert() {
final Thing individual = mcService.getInTransaction(individualIri);

assertNotNull(individual);
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE.getIri(), HQDM.INDIVIDUAL.getIri()));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF.getIri(), "classOfIndividual"));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD.getIri(), "possible world"));
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE, HQDM.INDIVIDUAL));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF, classOfIndividualIri));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri));

// Invert the operations and apply them in reverse order.
mcService.runInTransaction(DbChangeSet.invert(createIndividual));
Original file line number Diff line number Diff line change
@@ -48,11 +48,12 @@ public void testCreateAndDelete() {

// Create an operation to add an object with dummy values.
final IRI individualIri = new IRI(TEST_BASE, "individual");
final IRI classOfIndividualIri = new IRI(TEST_BASE, "classOfIndividual");

final DbCreateOperation createIndividual = new DbCreateOperation(individualIri, RDFS.RDF_TYPE,
HQDM.INDIVIDUAL.getIri());
HQDM.INDIVIDUAL);
final DbCreateOperation createIndividualMemberOf = new DbCreateOperation(individualIri, HQDM.MEMBER_OF,
"classOfIndividual");
classOfIndividualIri);

// Apply the operations.
mcService.runInTransaction(createIndividual);
@@ -62,13 +63,13 @@ public void testCreateAndDelete() {
final Thing individual = mcService.getInTransaction(individualIri);

assertNotNull(individual);
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE.getIri(), HQDM.INDIVIDUAL.getIri()));
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE, HQDM.INDIVIDUAL));

// Invert the operation and assert that it is no longer present.
mcService.runInTransaction(DbCreateOperation.invert(createIndividualMemberOf));

final Thing individualFromDb = mcService.getInTransaction(individualIri);
assertFalse(individualFromDb.hasThisValue(HQDM.MEMBER_OF.getIri(), "classOfIndividual"));
assertFalse(individualFromDb.hasThisValue(HQDM.MEMBER_OF, classOfIndividualIri));
}

/**
@@ -83,7 +84,7 @@ public void testMultipleCreateAndDelete() {

// Create operations to create an object.
final DbCreateOperation createIndividual = new DbCreateOperation(individualIri, RDFS.RDF_TYPE,
HQDM.INDIVIDUAL.getIri());
HQDM.INDIVIDUAL);
final DbCreateOperation createIndividualMemberOf = new DbCreateOperation(individualIri, HQDM.MEMBER_OF,
"classOfIndividual");
final DbCreateOperation createIndividualPartOfPossibleWorld = new DbCreateOperation(individualIri,
@@ -98,9 +99,9 @@ public void testMultipleCreateAndDelete() {
final Thing individual = mcService.getInTransaction(individualIri);

assertNotNull(individual);
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE.getIri(), HQDM.INDIVIDUAL.getIri()));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF.getIri(), "classOfIndividual"));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD.getIri(), "possible world"));
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE, HQDM.INDIVIDUAL));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF, "classOfIndividual"));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD, "possible world"));

// Invert two of the operations, apply them in reverse order and assert they are no longer present.
mcService.runInTransaction(DbCreateOperation.invert(createIndividualPartOfPossibleWorld));
@@ -122,7 +123,7 @@ public void testCreateWhenAlreadyPresent() {

// Create an operation to add an object with dummy values.
final DbCreateOperation createIndividual = new DbCreateOperation(individualIri, RDFS.RDF_TYPE,
HQDM.INDIVIDUAL.getIri());
HQDM.INDIVIDUAL);

// Apply the operation twice, the second should throw an exception.
mcService.runInTransaction(createIndividual);
Original file line number Diff line number Diff line change
@@ -46,17 +46,20 @@ public void testApplyAndInvert() {

final IRI individualIri = new IRI(TEST_BASE, "individual");
final IRI personIri = new IRI(TEST_BASE, "person");
final IRI classOfIndividualIri = new IRI(TEST_BASE, "classOfIndividual");
final IRI classOfPersonIri = new IRI(TEST_BASE, "classOfPerson");
final IRI possibleWorldIri = new IRI(TEST_BASE, "possible_world");

// Create operations to add an object with dummy values.
final DbChangeSet createIndividual = new DbChangeSet(List.of(),
List.of(new DbCreateOperation(individualIri, RDFS.RDF_TYPE, HQDM.INDIVIDUAL.getIri()),
new DbCreateOperation(individualIri, HQDM.MEMBER_OF, "classOfIndividual"),
new DbCreateOperation(individualIri, HQDM.PART_OF_POSSIBLE_WORLD, "possible world")));
List.of(new DbCreateOperation(individualIri, RDFS.RDF_TYPE, HQDM.INDIVIDUAL),
new DbCreateOperation(individualIri, HQDM.MEMBER_OF, classOfIndividualIri),
new DbCreateOperation(individualIri, HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri)));

final DbChangeSet createPerson = new DbChangeSet(List.of(),
List.of(new DbCreateOperation(personIri, RDFS.RDF_TYPE, HQDM.PERSON.getIri()),
new DbCreateOperation(personIri, HQDM.MEMBER_OF, "classOfPerson"),
new DbCreateOperation(personIri, HQDM.PART_OF_POSSIBLE_WORLD, "possible world")));
List.of(new DbCreateOperation(personIri, RDFS.RDF_TYPE, HQDM.PERSON),
new DbCreateOperation(personIri, HQDM.MEMBER_OF, classOfPersonIri),
new DbCreateOperation(personIri, HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri)));

final DbTransformation transformation = new DbTransformation(List.of(createIndividual, createPerson));

@@ -67,16 +70,16 @@ public void testApplyAndInvert() {
final Thing individual = mcService.getInTransaction(individualIri);

assertNotNull(individual);
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE.getIri(), HQDM.INDIVIDUAL.getIri()));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF.getIri(), "classOfIndividual"));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD.getIri(), "possible world"));
assertTrue(individual.hasThisValue(RDFS.RDF_TYPE, HQDM.INDIVIDUAL));
assertTrue(individual.hasThisValue(HQDM.MEMBER_OF, classOfIndividualIri));
assertTrue(individual.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri));

final Thing person = mcService.getInTransaction(personIri);

assertNotNull(person);
assertTrue(person.hasThisValue(RDFS.RDF_TYPE.getIri(), HQDM.PERSON.getIri()));
assertTrue(person.hasThisValue(HQDM.MEMBER_OF.getIri(), "classOfPerson"));
assertTrue(person.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD.getIri(), "possible world"));
assertTrue(person.hasThisValue(RDFS.RDF_TYPE, HQDM.PERSON));
assertTrue(person.hasThisValue(HQDM.MEMBER_OF, classOfPersonIri));
assertTrue(person.hasThisValue(HQDM.PART_OF_POSSIBLE_WORLD, possibleWorldIri));

// Invert the operations, apply them in reverse order and assert they are no longer present.
mcService.runInTransaction(transformation.invert());
Loading