PowsyblWriterSequenceFixTest.java
/**
* Copyright (c) 2019, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.triplestore.impl.rdf4j.test;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.MemDataSource;
import com.powsybl.triplestore.api.PropertyBag;
import com.powsybl.triplestore.api.PropertyBags;
import com.powsybl.triplestore.impl.rdf4j.TripleStoreRDF4J;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.builder.Input;
import org.xmlunit.diff.Diff;
import javax.xml.transform.Source;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
*/
class PowsyblWriterSequenceFixTest {
private final PropertyBags objects = new PropertyBags();
private final List<String> objectProperties = Arrays.asList("id", "property1", "property2");
private final String namespace = "http://test/";
private final String contextName = "context1";
private final String qualifiedContextName = "http://test/" + contextName;
private final String objectType = "http://test/type1";
private final String expected = "/fix-powsybl-writer-objects.xml";
@BeforeEach
void setUp() {
PropertyBag object = new PropertyBag(objectProperties, true);
object.put("id", "object1");
object.put("property1", "object1-property1-value");
object.put("property2", "object1-property2-value");
objects.add(object);
object = new PropertyBag(objectProperties, true);
object.put("id", "object2");
object.put("property1", "object2-property1-value");
object.put("property2", "object2-property2-value");
objects.add(object);
}
// The statements to be written by PowsyblWriter are stored in a Model,
// Model extends from a Set<Statement>,
// so no particular iteration order is guaranteed
// PowsyblWriter tries to group together all the predicates of the same subject,
// it needs to handle the statements in a particular order:
// all statements of same subject received in sequence,
// the first one being an rdf:type predicate that will be used to reify
// When we use Rio.write (flag writeBySubject == false),
// the statements will be sent to PowsyblWriters with the default iterator,
// if they have been inserted in an order that does not match the expected one,
// we will end up with an RDFHandlerException
// When we force TripleStoreRDF4J to write by subject
// (flag writeBySubject == true),
// it does not matter in which order the statements
// have been added to the triple store,
// all attempts to write them to XML should be successful
@Test
void testRioWriteWithBadAddSequence() {
assertThrows(RDFHandlerException.class, () -> test(false, this::addPropertiesObjects));
}
@Test
void testRioWriteWithProperObjectSequenceBadTypeSequence() {
assertThrows(RDFHandlerException.class, () -> test(false, this::addObjectPropertiesType));
}
@Test
void testRioWriteWithProperAddSequence() {
test(false, this::addObjectTypeProperties);
}
@Test
void testRioWriteOverrideWithBadAddSequence() {
test(true, this::addPropertiesObjects);
}
@Test
void testRioWriteOverrideWithProperObjectSequenceBadTypeSequence() {
test(true, this::addObjectPropertiesType);
}
private void test(boolean writeBySubject, BiConsumer<RepositoryConnection, Map<PropertyBag, IRI>> adder) {
TripleStoreRDF4J ts = new TripleStoreRDF4J();
ts.setWriteBySubject(writeBySubject);
addStatements(ts, adder);
writeAndCompareWithExpected(ts);
}
private void writeAndCompareWithExpected(TripleStoreRDF4J ts) {
DataSource ds = new MemDataSource();
ts.write(ds);
try (InputStream is = ds.newInputStream(contextName)) {
assertXmlEquals(getClass().getResourceAsStream(expected), is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void assertXmlEquals(InputStream expected, InputStream actual) {
Source sexpected = Input.fromStream(expected).build();
Source sactual = Input.fromStream(actual).build();
Diff myDiff = DiffBuilder
.compare(sexpected)
.withTest(sactual)
.ignoreWhitespace()
.ignoreComments()
.build();
boolean hasDiff = myDiff.hasDifferences();
if (hasDiff) {
LOG.error(myDiff.toString());
}
assertFalse(hasDiff);
}
private void addStatements(TripleStoreRDF4J ts, BiConsumer<RepositoryConnection, Map<PropertyBag, IRI>> adder) {
try (RepositoryConnection cnx = ts.getRepository().getConnection()) {
cnx.setIsolationLevel(IsolationLevels.NONE);
Map<PropertyBag, IRI> objectSubject = new HashMap<>();
adder.accept(cnx, objectSubject);
}
}
private void addObjectPropertiesType(RepositoryConnection cnx, Map<PropertyBag, IRI> objectSubject) {
objects.forEach(object -> {
objectProperties.forEach(property -> {
addStatement(cnx, property, object, objectSubject);
});
addObjectTypeStatement(
cnx,
subject(cnx, object, objectSubject),
objectType);
});
}
private void addObjectTypeProperties(RepositoryConnection cnx, Map<PropertyBag, IRI> objectSubject) {
objects.forEach(object -> {
addObjectTypeStatement(
cnx,
subject(cnx, object, objectSubject),
objectType);
objectProperties.forEach(property -> {
addStatement(cnx, property, object, objectSubject);
});
});
}
private void addPropertiesObjects(RepositoryConnection cnx, Map<PropertyBag, IRI> objectSubject) {
objects.forEach(object -> addObjectTypeStatement(
cnx,
subject(cnx, object, objectSubject),
objectType));
objectProperties.forEach(property -> {
objects.forEach(object -> {
addStatement(cnx, property, object, objectSubject);
});
});
}
private void addStatement(
RepositoryConnection cnx,
String property,
PropertyBag object,
Map<PropertyBag, IRI> objectSubject) {
IRI predicate = cnx.getValueFactory().createIRI(objectType + "." + property);
Literal value = cnx.getValueFactory().createLiteral(object.get(property));
Statement st = cnx.getValueFactory().createStatement(
subject(cnx, object, objectSubject),
predicate,
value);
Resource context = cnx.getValueFactory().createIRI(qualifiedContextName);
cnx.add(st, context);
}
private void addObjectTypeStatement(RepositoryConnection cnx, IRI subject, String objectType) {
IRI objectTypeIRI = cnx.getValueFactory().createIRI(objectType);
Statement subjectTypeStatement = cnx.getValueFactory().createStatement(
subject,
RDF.TYPE,
objectTypeIRI);
Resource context = cnx.getValueFactory().createIRI(qualifiedContextName);
cnx.add(subjectTypeStatement, context);
}
private IRI subject(
RepositoryConnection cnx,
PropertyBag object,
Map<PropertyBag, IRI> objectSubject) {
return objectSubject.computeIfAbsent(
object,
o -> cnx.getValueFactory().createIRI(namespace + o.get("id")));
}
private static final Logger LOG = LoggerFactory.getLogger(PowsyblWriterSequenceFixTest.class);
}