diff --git a/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapper.java b/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapper.java index 2d8f7a3a1f..0ab9b637bf 100644 --- a/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapper.java +++ b/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapper.java @@ -2,6 +2,8 @@ import static java.util.Map.entry; import static org.eclipse.rdf4j.model.util.Values.literal; +import static org.molgenis.emx2.Constants.COMPOSITE_REF_SEPARATOR; +import static org.molgenis.emx2.Constants.SUBSELECT_SEPARATOR; import com.google.common.net.UrlEscapers; import java.time.LocalDateTime; @@ -9,7 +11,6 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Namespace; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.base.CoreDatatype; @@ -66,7 +67,7 @@ public class ColumnTypeRdfMapper { // RELATIONSHIP entry(ColumnType.REF, RdfColumnType.REFERENCE), entry(ColumnType.REF_ARRAY, RdfColumnType.REFERENCE), - entry(ColumnType.REFBACK, RdfColumnType.REFERENCE), + entry(ColumnType.REFBACK, RdfColumnType.REFBACK), // LAYOUT and other constants entry(ColumnType.HEADING, RdfColumnType.SKIP), // Should not be in RDF output. @@ -108,10 +109,8 @@ public static CoreDatatype.XSD getCoreDataType(ColumnType columnType) { * */ public Set retrieveValues(final Row row, final Column column) { - if (row.getString(column.getName()) == null) { - return Set.of(); - } - return mapping.get(column.getColumnType()).retrieveValues(baseURL, row, column); + RdfColumnType mapper = mapping.get(column.getColumnType()); + return (mapper.isEmpty(row, column) ? Set.of() : mapper.retrieveValues(baseURL, row, column)); } private enum RdfColumnType { @@ -211,40 +210,65 @@ Set retrieveValues(String baseURL, Row row, Column column) { REFERENCE(CoreDatatype.XSD.ANYURI) { @Override Set retrieveValues(String baseURL, Row row, Column column) { - final TableMetadata target = column.getRefTable(); - final String rootTableName = - UrlEscapers.urlPathSegmentEscaper().escape(target.getRootTable().getIdentifier()); - final Namespace ns = getSchemaNamespace(baseURL, target.getRootTable().getSchema()); - - final Set iris = new HashSet<>(); - final Map> items = new HashMap<>(); - for (final Reference reference : column.getReferences()) { - final String localColumn = reference.getName(); - final String targetColumn = reference.getRefTo(); - if (column.isArray()) { - final String[] values = row.getStringArray(localColumn); - if (values != null) { - for (int i = 0; i < values.length; i++) { - var keyValuePairs = items.getOrDefault(i, new LinkedHashMap<>()); - keyValuePairs.put(targetColumn, values[i]); - items.put(i, keyValuePairs); - } - } + Map colNameToRefTableColName = + column.getReferences().stream() + .collect(Collectors.toMap(Reference::getName, Reference::getRefTo)); + return RdfColumnType.retrieveReferenceValues( + baseURL, row, column, colNameToRefTableColName); + } + + @Override + boolean isEmpty(Row row, Column column) { + // Composite key requires all fields to be filled. If one is null, all should be null. + return row.getString(column.getReferences().get(0).getName()) == null; + } + }, + REFBACK(CoreDatatype.XSD.ANYURI) { + @Override + Set retrieveValues(String baseURL, Row row, Column column) { + Map colNameToRefTableColName = new HashMap<>(); + if (row.getString(column.getName()) != null) { + colNameToRefTableColName.put( + column.getName(), column.getRefTable().getPrimaryKeyColumns().get(0).getName()); + } else { + refBackSubColumns( + colNameToRefTableColName, column, column.getName() + SUBSELECT_SEPARATOR, ""); + } + + return RdfColumnType.retrieveReferenceValues( + baseURL, row, column, colNameToRefTableColName); + } + + private void refBackSubColumns( + Map colNameToRefTableColName, + Column column, + String colPrefix, + String refPrefix) { + for (Column refPrimaryKey : column.getRefTable().getPrimaryKeyColumns()) { + if (refPrimaryKey.isRef() || refPrimaryKey.isRefArray()) { + refBackSubColumns( + colNameToRefTableColName, + refPrimaryKey, + colPrefix + refPrimaryKey.getName() + SUBSELECT_SEPARATOR, + refPrefix + refPrimaryKey.getName() + COMPOSITE_REF_SEPARATOR); } else { - final String value = row.getString(localColumn); - if (value != null) { - var keyValuePairs = items.getOrDefault(0, new LinkedHashMap<>()); - keyValuePairs.put(targetColumn, value); - items.put(0, keyValuePairs); - } + colNameToRefTableColName.put( + colPrefix + refPrimaryKey.getName(), refPrefix + refPrimaryKey.getName()); } } + } - for (final var item : items.values()) { - PrimaryKey key = new PrimaryKey(item); - iris.add(Values.iri(ns, rootTableName + "?" + key.getEncodedValue())); - } - return Set.copyOf(iris); + @Override + boolean isEmpty(Row row, Column column) { + if (row.getString(column.getName()) != null) return false; + + // Composite key requires all fields to be filled. If one is null, all should be null. + Optional firstMatch = + row.getColumnNames().stream() + .filter(i -> i.startsWith(column.getName() + SUBSELECT_SEPARATOR)) + .findFirst(); + + return firstMatch.isEmpty() || row.getString(firstMatch.get()) == null; } }, ONTOLOGY(CoreDatatype.XSD.ANYURI) { @@ -289,7 +313,7 @@ private static Namespace getSchemaNamespace(final String baseURL, final SchemaMe private static Set basicRetrieval(Object[] object, Function function) { return Arrays.stream(object) .map(value -> (Value) function.apply(value)) - .collect(Collectors.toSet()); + .collect(Collectors.toUnmodifiableSet()); } /** @@ -305,9 +329,47 @@ private static Set basicRetrievalString( String[] object, Function function) { return Arrays.stream(object) .map(value -> (Value) function.apply(value)) - .collect(Collectors.toSet()); + .collect(Collectors.toUnmodifiableSet()); } abstract Set retrieveValues(final String baseURL, final Row row, final Column column); + + boolean isEmpty(final Row row, final Column column) { + return row.getString(column.getName()) == null; + } + + private static Set retrieveReferenceValues( + final String baseURL, + final Row row, + final Column tableColumn, + final Map colNameToRefTableColName) { + final TableMetadata target = tableColumn.getRefTable(); + final String rootTableName = + UrlEscapers.urlPathSegmentEscaper().escape(target.getRootTable().getIdentifier()); + final Namespace ns = getSchemaNamespace(baseURL, target.getRootTable().getSchema()); + + final Map> items = new HashMap<>(); + for (final String colName : colNameToRefTableColName.keySet()) { + final String[] values = + (tableColumn.isArray() + ? row.getStringArray(colName) + : new String[] {row.getString(colName)}); + + if (values == null) continue; + + for (int i = 0; i < values.length; i++) { + Map keyValuePairs = items.getOrDefault(i, new LinkedHashMap<>()); + keyValuePairs.put(colNameToRefTableColName.get(colName), values[i]); + items.put(i, keyValuePairs); + } + } + + final Set values = new HashSet<>(); + for (final Map item : items.values()) { + PrimaryKey key = new PrimaryKey(item); + values.add(Values.iri(ns, rootTableName + "?" + key.getEncodedValue())); + } + return Set.copyOf(values); + } } } diff --git a/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/RDFService.java b/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/RDFService.java index e74e9f278a..14e43f812d 100644 --- a/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/RDFService.java +++ b/backend/molgenis-emx2-rdf/src/main/java/org/molgenis/emx2/rdf/RDFService.java @@ -599,8 +599,10 @@ private List getRows(Table table, final String rowId) { for (Column c : table.getMetadata().getColumns()) { if (c.isFile()) { selectColumns.add(s(c.getName(), s("id"), s("filename"), s("mimetype"))); - } else if (c.isReference()) { + } else if (c.isRef() || c.isRefArray()) { c.getReferences().forEach(i -> selectColumns.add(s(i.getName()))); + } else if (c.isRefback()) { + selectColumns.add(refBackSelect(c)); } else { selectColumns.add(s(c.getName())); } @@ -627,6 +629,18 @@ private List getRows(Table table, final String rowId) { } } + private SelectColumn refBackSelect(Column column) { + List subSelects = new ArrayList<>(); + for (Column subColumn : column.getRefTable().getPrimaryKeyColumns()) { + if (subColumn.isRef() || subColumn.isRefArray()) { + subSelects.add(refBackSelect(subColumn)); + } else { + subSelects.add(s(subColumn.getName())); + } + } + return s(column.getName(), subSelects.toArray(SelectColumn[]::new)); + } + private IRI getIriForRow(final Row row, final Table table) { return getIriForRow(row, table.getMetadata()); } diff --git a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapperTest.java b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapperTest.java index 0b1021b645..41e2e9ae5a 100644 --- a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapperTest.java +++ b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/ColumnTypeRdfMapperTest.java @@ -1,7 +1,12 @@ package org.molgenis.emx2.rdf; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.molgenis.emx2.Column.column; import static org.molgenis.emx2.Row.row; +import static org.molgenis.emx2.SelectColumn.s; import static org.molgenis.emx2.TableMetadata.table; import java.io.File; @@ -9,10 +14,8 @@ import java.util.stream.Collectors; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.base.CoreDatatype; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.model.util.Values; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.molgenis.emx2.*; @@ -27,19 +30,27 @@ class ColumnTypeRdfMapperTest { static final String TEST_TABLE = "TestTable"; static final String REF_TABLE = "TestRefTable"; static final String REFBACK_TABLE = "TestRefBackTable"; + static final String COMPOSITE_REF_TABLE = "TestCompositeRefTable"; + static final String COMPOSITE_REFBACK_TABLE = "TestCompositeRefbackTable"; static final String ONT_TABLE = "TestOntology"; + + static final String BASE_URL = "http://localhost:8080/"; + static final String RDF_API_URL_PREFIX = BASE_URL + TEST_SCHEMA + "/api/rdf/"; + static final String FILE_API_URL_PREFIX = BASE_URL + TEST_SCHEMA + "/api/file/"; + + static final ColumnTypeRdfMapper mapper = new ColumnTypeRdfMapper(BASE_URL); + static final ClassLoader classLoader = ColumnTypeRdfMapperTest.class.getClassLoader(); - static final SimpleValueFactory factory = SimpleValueFactory.getInstance(); - static final String baseUrl = "http://localhost:8080/"; - static final String rdfApiUrlPrefix = baseUrl + TEST_SCHEMA + "/api/rdf/"; - static final String fileApiUrlPrefix = baseUrl + TEST_SCHEMA + "/api/file/"; - static final ColumnTypeRdfMapper mapper = new ColumnTypeRdfMapper(baseUrl); static final File TEST_FILE = new File(classLoader.getResource("testfiles/molgenis.png").getFile()); + static final String COLUMN_COMPOSITE_REF = "composite_ref"; + static final String COLUMN_COMPOSITE_REF_ARRAY = "composite_ref_array"; + static final String COLUMN_COMPOSITE_REFBACK = "composite_refback"; + static Database database; static Schema allColumnTypes; - static Row firstRow; + static List testRows; @BeforeAll public static void setup() { @@ -49,39 +60,53 @@ public static void setup() { // Generates a column for each ColumnType. // Filters out REFBACK so that it can be added as last step when all REFs are generated. - List columns = - new ArrayList<>( - Arrays.stream(ColumnType.values()) - .map((value) -> column(value.name(), value)) - .filter((column -> !column.getColumnType().equals(ColumnType.REFBACK))) - .toList()); + List columnList = + Arrays.stream(ColumnType.values()) + .map((value) -> column(value.name(), value)) + .collect(Collectors.toList()); // Defines column-specific settings. - for (Column column : columns) { + for (Column column : columnList) { switch (column.getColumnType()) { case STRING -> column.setPkey(); case REF, REF_ARRAY -> column.setRefTable(REF_TABLE); + case REFBACK -> column.setRefTable(REFBACK_TABLE).setRefBack("ref"); case ONTOLOGY, ONTOLOGY_ARRAY -> column.setRefTable(ONT_TABLE); } } - // refback is possible in one go since 26 nov 2024 :-) - columns.add( - column(ColumnType.REFBACK.name(), ColumnType.REFBACK) - .setRefTable(REFBACK_TABLE) + + // Add extra custom columns for additional tests. + columnList.add(column(COLUMN_COMPOSITE_REF, ColumnType.REF).setRefTable(COMPOSITE_REF_TABLE)); + columnList.add( + column(COLUMN_COMPOSITE_REF_ARRAY, ColumnType.REF_ARRAY).setRefTable(COMPOSITE_REF_TABLE)); + columnList.add( + column(COLUMN_COMPOSITE_REFBACK, ColumnType.REFBACK) + .setRefTable(COMPOSITE_REFBACK_TABLE) .setRefBack("ref")); // Creates tables. allColumnTypes.create( // Ontology table table(ONT_TABLE).setTableType(TableType.ONTOLOGIES), + // Table to test on + table(TEST_TABLE, columnList.toArray(Column[]::new)), // Table to ref towards table(REF_TABLE, column("id", ColumnType.STRING).setPkey()), - // Table to test on - table(TEST_TABLE, columns.toArray(new Column[0])), // Table to get refbacks from table( REFBACK_TABLE, column("id", ColumnType.STRING).setPkey(), + column("ref", ColumnType.REF).setRefTable(TEST_TABLE)), + // Table containing composite primary key to ref towards + table( + COMPOSITE_REF_TABLE, + column("ids", ColumnType.STRING).setPkey(), + column("idi", ColumnType.INT).setPkey()), + // Table containing composite primary key to get refback from + table( + COMPOSITE_REFBACK_TABLE, + column("id1", ColumnType.STRING).setPkey(), + column("id2", ColumnType.STRING).setPkey(), column("ref", ColumnType.REF).setRefTable(TEST_TABLE))); // Inserts table data @@ -94,6 +119,11 @@ public static void setup() { allColumnTypes.getTable(REF_TABLE).insert(row("id", "1"), row("id", "2"), row("id", "3")); + allColumnTypes + .getTable(COMPOSITE_REF_TABLE) + .insert( + row("ids", "a", "idi", "1"), row("ids", "b", "idi", "2"), row("ids", "c", "idi", "3")); + allColumnTypes .getTable(TEST_TABLE) .insert( @@ -167,20 +197,43 @@ public static void setup() { ColumnType.HYPERLINK.name(), "https://molgenis.org", ColumnType.HYPERLINK_ARRAY.name(), - "https://molgenis.org, https://github.com/molgenis")); + "https://molgenis.org, https://github.com/molgenis", + // Extra columns for composite key testing + // -- no manual entry: COLUMN_COMPOSITE_REFBACK + COLUMN_COMPOSITE_REF + ".ids", + "a", + COLUMN_COMPOSITE_REF + ".idi", + "1", + COLUMN_COMPOSITE_REF_ARRAY + ".ids", + "b,c", + COLUMN_COMPOSITE_REF_ARRAY + ".idi", + "2,3"), + // Empty row for validating correct empty behaviour (only primary key & AUTO_ID present) + row(ColumnType.STRING.name(), "emptyValuesRow")); allColumnTypes.getTable(REFBACK_TABLE).insert(row("id", "1", "ref", "lonelyString")); + allColumnTypes + .getTable(COMPOSITE_REFBACK_TABLE) + .insert( + row("id1", "a", "id2", "b", "ref", "lonelyString"), + row("id1", "c", "id2", "d", "ref", "lonelyString")); // Use query to explicitly retrieve all rows as the following would exclude REFBACK values: // allColumnTypes.getTable(TEST_TABLE).retrieveRows() - SelectColumn[] selectColumns = + List selectColumnList = Arrays.stream(ColumnType.values()) .map(i -> new SelectColumn(i.name())) - .toArray(SelectColumn[]::new); + .collect(Collectors.toList()); + // Add Composite columns manually. + selectColumnList.add(s(COLUMN_COMPOSITE_REF + ".ids")); + selectColumnList.add(s(COLUMN_COMPOSITE_REF + ".idi")); + selectColumnList.add(s(COLUMN_COMPOSITE_REF_ARRAY + ".ids")); + selectColumnList.add(s(COLUMN_COMPOSITE_REF_ARRAY + ".idi")); + selectColumnList.add(s(COLUMN_COMPOSITE_REFBACK, s("id1"), s("id2"))); + SelectColumn[] selectColumns = selectColumnList.toArray(SelectColumn[]::new); - // Describe first row for easy access. - firstRow = - allColumnTypes.getTable(TEST_TABLE).query().select(selectColumns).retrieveRows().get(0); + // Describes rows for easy access. + testRows = allColumnTypes.getTable(TEST_TABLE).query().select(selectColumns).retrieveRows(); } @AfterAll @@ -190,8 +243,19 @@ public static void tearDown() { } private Set retrieveValues(String columnName) { + return retrieveValues(columnName, 0); + } + + /** Only primary key & AUTO_ID is filled. */ + private Set retrieveEmptyValues(String columnName) { + // REFBACK causes duplicate row (with only REFBACK values being different). + // Therefore, 3rd row is empty one. + return retrieveValues(columnName, 2); + } + + private Set retrieveValues(String columnName, int row) { return mapper.retrieveValues( - firstRow, allColumnTypes.getTable(TEST_TABLE).getMetadata().getColumn(columnName)); + testRows.get(row), allColumnTypes.getTable(TEST_TABLE).getMetadata().getColumn(columnName)); } private Value retrieveFirstValue(String columnName) { @@ -207,157 +271,161 @@ void validateAllColumnTypesCovered() { Set columnTypes = Arrays.stream(ColumnType.values()).collect(Collectors.toSet()); Set columnMappings = ColumnTypeRdfMapper.getMapperKeys(); - Assertions.assertEquals(columnTypes, columnMappings); + assertEquals(columnTypes, columnMappings); } /** - * Validates if {@link org.eclipse.rdf4j.model.Value} is of expected type. Only validates the - * non-array {@link ColumnType}{@code s} (as array-versions should be of identical type). + * Validates if {@link Value} is of expected type. Only validates the non-array {@link + * ColumnType}{@code s} (as array-versions should be of identical type). */ @Test void validateValueTypes() { Row row = allColumnTypes.getTable(TEST_TABLE).retrieveRows().get(0); - Assertions.assertAll( + assertAll( // SIMPLE - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.BOOL.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.UUID.name()).isIRI()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.FILE.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.BOOL.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.UUID.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.FILE.name()).isIRI()), // STRING - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.STRING.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.TEXT.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.JSON.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.STRING.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.TEXT.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.JSON.name()).isLiteral()), // NUMERIC - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.INT.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.LONG.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.DECIMAL.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.DATE.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.DATETIME.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.PERIOD.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.INT.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.LONG.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.DECIMAL.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.DATE.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.DATETIME.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.PERIOD.name()).isLiteral()), // RELATIONSHIP - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.REF.name()).isIRI()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.REFBACK.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.REF.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.REFBACK.name()).isIRI()), // LAYOUT and other constants // ColumnType.HEADING.name() -> no Value should be present to validate on // format flavors that extend a baseType - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.AUTO_ID.name()).isLiteral()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.ONTOLOGY.name()).isIRI()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.EMAIL.name()).isIRI()), - () -> Assertions.assertTrue(retrieveFirstValue(ColumnType.HYPERLINK.name()).isIRI())); + () -> assertTrue(retrieveFirstValue(ColumnType.AUTO_ID.name()).isLiteral()), + () -> assertTrue(retrieveFirstValue(ColumnType.ONTOLOGY.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.EMAIL.name()).isIRI()), + () -> assertTrue(retrieveFirstValue(ColumnType.HYPERLINK.name()).isIRI()), + + // Composite keys + () -> assertTrue(retrieveFirstValue(COLUMN_COMPOSITE_REF).isIRI()), + () -> assertTrue(retrieveFirstValue(COLUMN_COMPOSITE_REFBACK).isIRI())); } @Test void validateValuesRetrieval() { - Assertions.assertAll( + // REFBACK is special usecase that returns multiple rows if multiple matches are found where + // all columns are identical except the REFBACK. + HashSet actualRefback = new HashSet<>(); + for (int i = 0; i < testRows.size() - 1; i++) { // Last row is empty row. + actualRefback.addAll(retrieveValues(COLUMN_COMPOSITE_REFBACK, i)); + } + + // Validation + assertAll( // SIMPLE + () -> assertEquals(Set.of(Values.literal(true)), retrieveValues(ColumnType.BOOL.name())), () -> - Assertions.assertEquals( - Set.of(Values.literal(true)), retrieveValues(ColumnType.BOOL.name())), - () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal(true), Values.literal(false)), retrieveValues(ColumnType.BOOL_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.iri("urn:uuid:e8af409e-86f7-11ef-85b2-6b76fd707d70")), retrieveValues(ColumnType.UUID.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.iri("urn:uuid:e8af409e-86f7-11ef-85b2-6b76fd707d70"), Values.iri("urn:uuid:14bfb4ca-86f8-11ef-8cc0-378b59fe72e8")), retrieveValues(ColumnType.UUID_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.iri( - fileApiUrlPrefix + FILE_API_URL_PREFIX + TEST_TABLE + "/" + ColumnType.FILE.name() + "/" // Not sure how to retrieve more directly as changes everytime - + firstRow.getString(ColumnType.FILE.name()))), + + testRows.get(0).getString(ColumnType.FILE.name()))), retrieveValues(ColumnType.FILE.name())), // STRING () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("lonelyString", CoreDatatype.XSD.STRING)), retrieveValues(ColumnType.STRING.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.literal("string1", CoreDatatype.XSD.STRING), Values.literal("string2", CoreDatatype.XSD.STRING)), retrieveValues(ColumnType.STRING_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("lonelyText", CoreDatatype.XSD.STRING)), retrieveValues(ColumnType.TEXT.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.literal("text1", CoreDatatype.XSD.STRING), Values.literal("text2", CoreDatatype.XSD.STRING)), retrieveValues(ColumnType.TEXT_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("{\"a\":1,\"b\":2}", CoreDatatype.XSD.STRING)), retrieveValues(ColumnType.JSON.name())), // NUMERIC + () -> assertEquals(Set.of(Values.literal(0)), retrieveValues(ColumnType.INT.name())), () -> - Assertions.assertEquals( - Set.of(Values.literal(0)), retrieveValues(ColumnType.INT.name())), - () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal(1), Values.literal(2)), retrieveValues(ColumnType.INT_ARRAY.name())), + () -> assertEquals(Set.of(Values.literal(3L)), retrieveValues(ColumnType.LONG.name())), () -> - Assertions.assertEquals( - Set.of(Values.literal(3L)), retrieveValues(ColumnType.LONG.name())), - () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal(4L), Values.literal(5L)), retrieveValues(ColumnType.LONG_ARRAY.name())), + () -> assertEquals(Set.of(Values.literal(0.5D)), retrieveValues(ColumnType.DECIMAL.name())), () -> - Assertions.assertEquals( - Set.of(Values.literal(0.5D)), retrieveValues(ColumnType.DECIMAL.name())), - () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal(1.5D), Values.literal(2.5D)), retrieveValues(ColumnType.DECIMAL_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("2000-01-01", CoreDatatype.XSD.DATE)), retrieveValues(ColumnType.DATE.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.literal("2001-01-01", CoreDatatype.XSD.DATE), Values.literal("2002-01-01", CoreDatatype.XSD.DATE)), retrieveValues(ColumnType.DATE_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("3000-01-01T12:30:00", CoreDatatype.XSD.DATETIME)), retrieveValues(ColumnType.DATETIME.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.literal("3001-01-01T12:30:00", CoreDatatype.XSD.DATETIME), Values.literal("3002-01-01T12:30:00", CoreDatatype.XSD.DATETIME)), retrieveValues(ColumnType.DATETIME_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.literal("P1D", CoreDatatype.XSD.DURATION)), retrieveValues(ColumnType.PERIOD.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.literal("P1M", CoreDatatype.XSD.DURATION), Values.literal("P1Y", CoreDatatype.XSD.DURATION)), @@ -365,57 +433,104 @@ void validateValuesRetrieval() { // RELATIONSHIP () -> - Assertions.assertEquals( - Set.of(Values.iri(rdfApiUrlPrefix + REF_TABLE + "?id=1")), + assertEquals( + Set.of(Values.iri(RDF_API_URL_PREFIX + REF_TABLE + "?id=1")), retrieveValues(ColumnType.REF.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( - Values.iri(rdfApiUrlPrefix + REF_TABLE + "?id=2"), - Values.iri(rdfApiUrlPrefix + REF_TABLE + "?id=3")), + Values.iri(RDF_API_URL_PREFIX + REF_TABLE + "?id=2"), + Values.iri(RDF_API_URL_PREFIX + REF_TABLE + "?id=3")), retrieveValues(ColumnType.REF_ARRAY.name())), () -> - Assertions.assertEquals( - Set.of(Values.iri(rdfApiUrlPrefix + REFBACK_TABLE + "?id=1")), + assertEquals( + Set.of(Values.iri(RDF_API_URL_PREFIX + REFBACK_TABLE + "?id=1")), retrieveValues(ColumnType.REFBACK.name())), // LAYOUT and other constants -> should return empty sets as they should be excluded - () -> Assertions.assertEquals(Set.of(), retrieveValues(ColumnType.HEADING.name())), + () -> assertEquals(Set.of(), retrieveValues(ColumnType.HEADING.name())), // format flavors that extend a baseType () -> // AUTO_ID is unique so full equality check not possible - Assertions.assertTrue( + assertTrue( retrieveValues(ColumnType.AUTO_ID.name()).stream() .findFirst() .get() .stringValue() .matches("[0-9a-zA-Z]+")), () -> - Assertions.assertEquals( - Set.of(Values.iri(rdfApiUrlPrefix + ONT_TABLE + "?name=aa")), + assertEquals( + Set.of(Values.iri(RDF_API_URL_PREFIX + ONT_TABLE + "?name=aa")), retrieveValues(ColumnType.ONTOLOGY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( - Values.iri(rdfApiUrlPrefix + ONT_TABLE + "?name=bb"), - Values.iri(rdfApiUrlPrefix + ONT_TABLE + "?name=cc")), + Values.iri(RDF_API_URL_PREFIX + ONT_TABLE + "?name=bb"), + Values.iri(RDF_API_URL_PREFIX + ONT_TABLE + "?name=cc")), retrieveValues(ColumnType.ONTOLOGY_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.iri("mailto:aap@example.com")), retrieveValues(ColumnType.EMAIL.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.iri("mailto:noot@example.com"), Values.iri("mailto:mies@example.com")), retrieveValues(ColumnType.EMAIL_ARRAY.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of(Values.iri("https://molgenis.org")), retrieveValues(ColumnType.HYPERLINK.name())), () -> - Assertions.assertEquals( + assertEquals( Set.of( Values.iri("https://molgenis.org"), Values.iri("https://github.com/molgenis")), - retrieveValues(ColumnType.HYPERLINK_ARRAY.name()))); + retrieveValues(ColumnType.HYPERLINK_ARRAY.name())), + // Composite reference / refback + () -> + assertEquals( + Set.of(Values.iri(RDF_API_URL_PREFIX + COMPOSITE_REF_TABLE + "?idi=1&ids=a")), + retrieveValues(COLUMN_COMPOSITE_REF)), + () -> + assertEquals( + Set.of( + Values.iri(RDF_API_URL_PREFIX + COMPOSITE_REF_TABLE + "?idi=2&ids=b"), + Values.iri(RDF_API_URL_PREFIX + COMPOSITE_REF_TABLE + "?idi=3&ids=c")), + retrieveValues(COLUMN_COMPOSITE_REF_ARRAY)), + () -> + assertEquals( + Set.of( + Values.iri(RDF_API_URL_PREFIX + COMPOSITE_REFBACK_TABLE + "?id1=a&id2=b"), + Values.iri(RDF_API_URL_PREFIX + COMPOSITE_REFBACK_TABLE + "?id1=c&id2=d")), + actualRefback)); + } + + @Test + void validateEmptyValuesRetrieval() { + HashSet emptySet = new HashSet<>(); + Column[] columns = + allColumnTypes.getTable(TEST_TABLE).getMetadata().getColumns().stream() + // Primary key and AUTO_ID are filled so skipped. + .filter(c -> !(c.isPrimaryKey() || c.getColumnType().equals(ColumnType.AUTO_ID))) + .toArray(Column[]::new); + + for (Column column : columns) { + Set actual = retrieveEmptyValues(column.getName()); + assertEquals( + emptySet, + actual, + column.getName() + " has a value while it should be empty: " + actual.toString()); + } + } + + @Test + void validateUnmodifiable() { + allColumnTypes.getTable(TEST_TABLE).getMetadata().getColumns().stream() + .forEach( + c -> { + assertThrows( + UnsupportedOperationException.class, + () -> retrieveValues(c.getName()).clear(), + c.getName() + " returns a modifiable set while it should be unmodifiable"); + }); } } diff --git a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/PrimaryKeyTest.java b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/PrimaryKeyTest.java index 479b1dfbaf..b6af565a4c 100644 --- a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/PrimaryKeyTest.java +++ b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/PrimaryKeyTest.java @@ -1,6 +1,10 @@ package org.molgenis.emx2.rdf; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.molgenis.emx2.FilterBean.f; import static org.molgenis.emx2.Operator.EQUALS; @@ -27,13 +31,7 @@ void testThatAPrimaryKeyIsSorted() { @Test void testThatAPrimaryKeyMustHaveAtLeastOneComponent() { - var pairs = new HashMap(); - try { - var key = new PrimaryKey(pairs); - assertNull(key, "Should have thrown an exception during initialisation"); - } catch (Exception e) { - // Expected - } + assertThrows(IllegalArgumentException.class, () -> new PrimaryKey(Map.of())); } @Test @@ -79,4 +77,15 @@ void testThatKeyCanBeConvertedToAFilter() { assertTrue(filterFirst, "The filter should contain a sub filter for the first key."); assertTrue(filterLast, "The filter should contain a sub filter for the last key."); } + + @Test + void testEncodedValues() { + assertAll( + () -> assertEquals("a=1", new PrimaryKey(Map.of("a", "1")).getEncodedValue()), + () -> assertEquals("a=1&b=2", new PrimaryKey(Map.of("a", "1", "b", "2")).getEncodedValue()), + () -> + assertEquals( + "a=1&b=2&c=3", + new PrimaryKey(Map.of("a", "1", "b", "2", "c", "3")).getEncodedValue())); + } } diff --git a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RDFTest.java b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RDFTest.java index 4339b6c865..2f2e71013b 100644 --- a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RDFTest.java +++ b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RDFTest.java @@ -4,16 +4,20 @@ import static org.molgenis.emx2.Column.column; import static org.molgenis.emx2.Row.row; import static org.molgenis.emx2.TableMetadata.table; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_CHILD1_FIRST; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_CHILD1_SECOND; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_GRANDCHILD1_FIRST; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_GRANDCHILD1_SECOND; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_ROOT1_FIRST; +import static org.molgenis.emx2.rdf.RDFTest.ValidationSubjects.COMP_ROOT2_FIRST; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.StringReader; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import org.eclipse.rdf4j.model.IRI; @@ -48,6 +52,9 @@ public class RDFTest { */ public static final String POOKY_ROWID = "name=pooky"; + static final String BASE_URL = "http://localhost:8080/"; + static final String RDF_API_LOCATION = "/api/rdf"; + /** Advanced setting field for adding custom RDF to the API. */ private static final String SETTING_CUSTOM_RDF = "custom_rdf"; @@ -55,7 +62,6 @@ public class RDFTest { static Database database; static List petStoreSchemas; - static final String RDF_API_LOCATION = "/api/rdf"; static Schema petStore_nr1; static Schema petStore_nr2; static Schema compositeKeyTest; @@ -96,30 +102,72 @@ public static void setup() { // Test schema for composite keys compositeKeyTest = database.dropCreateSchema(RDFTest.class.getSimpleName() + "_compositeKey"); compositeKeyTest.create( - table("Patients", column("firstName").setPkey(), column("lastName").setPkey()), + table("root1", column("r1").setType(ColumnType.REF).setRefTable("child1").setPkey()), + table( + "root2", + column("r2a").setPkey(), + column("r2b").setType(ColumnType.REF).setRefTable("child1").setPkey()), + table( + "child1", + column("c1a").setPkey(), + column("c1b").setType(ColumnType.REF).setRefTable("grandchild1").setPkey(), + column("grandchild1ref").setType(ColumnType.REF).setRefTable("grandchild1"), + column("root1refback") + .setType(ColumnType.REFBACK) + .setRefTable("root1") + .setRefBack("r1"), + column("root2refback") + .setType(ColumnType.REFBACK) + .setRefTable("root2") + .setRefBack("r2b")), table( - "Samples", - column("patient").setType(ColumnType.REF).setRefTable("Patients").setPkey(), - column("id").setPkey(), - column("someNonKeyRef").setType(ColumnType.REF_ARRAY).setRefTable("Samples"))); - compositeKeyTest.getTable("Patients").insert(row("firstName", "Donald", "lastName", "Duck")); + "grandchild1", + column("gc1a").setPkey(), + column("gc1b").setPkey(), + column("child1refback") + .setType(ColumnType.REFBACK) + .setRefTable("child1") + .setRefBack("c1b"))); + compositeKeyTest - .getTable("Samples") + .getTable("grandchild1") .insert( - row("patient.firstName", "Donald", "patient.lastName", "Duck", "id", "sample1"), + row("gc1a", "gc1a_first", "gc1b", "gc1b_first"), + row("gc1a", "gc1a_second", "gc1b", "gc1b_second")); + + compositeKeyTest + .getTable("child1") + .insert( + row("c1a", "c1a_first", "c1b.gc1a", "gc1a_first", "c1b.gc1b", "gc1b_first"), row( - "patient.firstName", - "Donald", - "patient.lastName", - "Duck", - "id", - "sample2", - "someNonKeyRef.patient.firstName", - "Donald", - "someNonKeyRef.patient.lastName", - "Duck", - "someNonKeyRef.id", - "sample1")); + "c1a", + "c1a_second", + "c1b.gc1a", + "gc1a_first", + "c1b.gc1b", + "gc1b_first", + "grandchild1ref.gc1a", + "gc1a_second", + "grandchild1ref.gc1b", + "gc1b_second")); + + compositeKeyTest + .getTable("root1") + .insert( + row("r1.c1a", "c1a_first", "r1.c1b.gc1a", "gc1a_first", "r1.c1b.gc1b", "gc1b_first")); + + compositeKeyTest + .getTable("root2") + .insert( + row( + "r2a", + "r2a_first", + "r2b.c1a", + "c1a_second", + "r2b.c1b.gc1a", + "gc1a_first", + "r2b.c1b.gc1b", + "gc1b_first")); // Test schema for ontologies ontologyTest = database.dropCreateSchema("OntologyTest"); @@ -178,6 +226,7 @@ public static void setup() { // Test table inheritance // Use example from the catalogue schema since this has all the different issues. + database.dropSchemaIfExists("tableInheritanceExternalSchemaTest"); // in case tearDown fails tableInherTest = database.dropCreateSchema("tableInheritanceTest"); tableInherTest.create( table( @@ -270,6 +319,10 @@ public static void setup() { refBackTest.getTable("tableRef").insert(row("id", "1", "link", "a")); } + private static String getApi(Schema schema) { + return BASE_URL + schema.getName() + RDF_API_LOCATION + "/"; + } + @AfterAll public static void tearDown() { database = TestDatabaseFactory.getTestDatabase(); @@ -396,31 +449,41 @@ void testThatRDFforColumnOnlyContainsMetadata() throws IOException { } @Test - void testThatCompositeKeysReferToDatabaseFields() throws IOException { + void testCompositeKeysPresenceOnFullSchema() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(compositeKeyTest), handler); - var subjectWithCompositeKey = - "http://localhost:8080/" - + compositeKeyTest.getName() - + "/api/rdf/Samples?id=sample1&patient.firstName=Donald&patient.lastName=Duck"; - var iris = handler.resources.keySet().stream().map(Objects::toString).toList(); - assertTrue( - iris.contains(subjectWithCompositeKey), - "A Sample resource should have a key based on patient.firstName, patient.lastName and id"); + + new RdfValidator() + .add(ValidationTriple.COMP_ROOT1_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_ROOT2_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_FIRST_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_FIRST_REFBACK_ROOT1.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_SECOND_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_SECOND_REFBACK_ROOT2.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_SECOND_NON_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_GRANDCHILD_REFBACK_1.getTriple(), true) + .add(ValidationTriple.COMP_GRANDCHILD_REFBACK_2.getTriple(), true) + .validate(handler); } @Test - void testThatRowCanBeFetchedByCompositeKey() throws IOException { + void testCompositeKeysRowSelection() throws IOException { var handler = new InMemoryRDFHandler() {}; - // Encoded version of patient.firstName=Donald & patient.lastName=Duck & id=sample1 - var rowId = "id=sample2&patient.firstName=Donald&patient.lastName=Duck"; - getAndParseRDF(Selection.ofRow(compositeKeyTest, "Samples", rowId), handler); - var subjectWithCompositeKey = - "http://localhost:8080/" + compositeKeyTest.getName() + "/api/rdf/Samples?" + rowId; - var iris = handler.resources.keySet().stream().map(Objects::toString).toList(); - assertTrue( - iris.contains(subjectWithCompositeKey), - "A Sample resource should have a key based on patient.firstName, patient.lastName and id"); + getAndParseRDF( + Selection.ofRow( + compositeKeyTest, "Child1", "c1a=c1a_second&c1b.gc1a=gc1a_first&c1b.gc1b=gc1b_first"), + handler); + new RdfValidator() + .add(ValidationTriple.COMP_ROOT1_KEY_REF.getTriple(), false) + .add(ValidationTriple.COMP_ROOT2_KEY_REF.getTriple(), false) + .add(ValidationTriple.COMP_CHILD1_FIRST_KEY_REF.getTriple(), false) + .add(ValidationTriple.COMP_CHILD1_FIRST_REFBACK_ROOT1.getTriple(), false) + .add(ValidationTriple.COMP_CHILD1_SECOND_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_SECOND_REFBACK_ROOT2.getTriple(), true) + .add(ValidationTriple.COMP_CHILD1_SECOND_NON_KEY_REF.getTriple(), true) + .add(ValidationTriple.COMP_GRANDCHILD_REFBACK_1.getTriple(), false) + .add(ValidationTriple.COMP_GRANDCHILD_REFBACK_2.getTriple(), false) + .validate(handler); } @Test @@ -533,7 +596,8 @@ void testThatURLsAreNotSplitForOntologyParentItem() throws IOException { getAndParseRDF(Selection.of(ontologyTest, "Diseases"), handler); var subject = Values.iri( - "http://localhost:8080/OntologyTest/api/rdf/Diseases?name=C00-C14+Malignant+neoplasms+of+lip%2C+oral+cavity+and+pharynx"); + getApi(ontologyTest) + + "Diseases?name=C00-C14+Malignant+neoplasms+of+lip%2C+oral+cavity+and+pharynx"); var parents = handler.resources.get(subject).get(RDFS.SUBCLASSOF); assertEquals( @@ -555,38 +619,32 @@ void testTableInheritanceAlwaysSamePredicate() throws IOException { () -> assertTrue( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "Root/column/rootColumn")), "There should be a predicate for the rootColumn in the Root table"), () -> assertFalse( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/Child/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "Child/column/rootColumn")), "There should not be a predicate for the rootColumn in the Child table"), () -> assertFalse( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/GrandchildTypeA/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "GrandchildTypeA/column/rootColumn")), "There should not be a predicate for the rootColumn in the GrandchildTypeA table"), () -> assertFalse( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/GrandchildTypeB/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "GrandchildTypeB/column/rootColumn")), "There should not be a predicate for the rootColumn in the GrandchildTypeB table"), () -> assertFalse( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/ExternalChild/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "ExternalChild/column/rootColumn")), "There should not be a predicate for the rootColumn in the ExternalChild table"), () -> assertFalse( handler.resources.containsKey( - Values.iri( - "http://localhost:8080/tableInheritanceTest/api/rdf/ExternalGrandchild/column/rootColumn")), + Values.iri(getApi(tableInherTest) + "ExternalGrandchild/column/rootColumn")), "There should not be a predicate for the rootColumn in the ExternalGrandchild table")); } @@ -594,38 +652,34 @@ void testTableInheritanceAlwaysSamePredicate() throws IOException { void testTableInheritanceRetrieveData() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, true), - Map.entry(ValidationTriple.ID2, true), - Map.entry(ValidationTriple.ID3, true), - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, false), // different schema - Map.entry(ValidationTriple.ID6, false), // different schema - Map.entry(ValidationTriple.UNRELATED, false) // different schema - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), true) + .add(ValidationTriple.INHER_ID2.getTriple(), true) + .add(ValidationTriple.INHER_ID3.getTriple(), true) + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID6.getTriple(), false) // different schema + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // different schema + .validate(handler); } @Test void testTableInheritanceRetrieveDataWithTableRoot() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest, "Root"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, true), - Map.entry(ValidationTriple.ID2, true), - Map.entry(ValidationTriple.ID3, true), - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, false), // different schema - Map.entry(ValidationTriple.ID6, false), // different schema - Map.entry(ValidationTriple.UNRELATED, false) // different schema - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), true) + .add(ValidationTriple.INHER_ID2.getTriple(), true) + .add(ValidationTriple.INHER_ID3.getTriple(), true) + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID6.getTriple(), false) // different schema + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // different schema + .validate(handler); } @Test @@ -633,19 +687,17 @@ void testTableInheritanceRetrieveDataWithTableChild() throws IOException { // All subjects still use Root IRIs but offers a way to "filter out parent triples". var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest, "Child"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // parent of selected table - Map.entry(ValidationTriple.ID2, true), - Map.entry(ValidationTriple.ID3, true), // child - Map.entry(ValidationTriple.ID4, true), // child - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), // child - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), // child - Map.entry(ValidationTriple.ID5, false), // different schema - Map.entry(ValidationTriple.ID6, false), // different schema - Map.entry(ValidationTriple.UNRELATED, false) // different schema - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // parent of selected table + .add(ValidationTriple.INHER_ID2.getTriple(), true) + .add(ValidationTriple.INHER_ID3.getTriple(), true) // child + .add(ValidationTriple.INHER_ID4.getTriple(), true) // child + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) // child + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) // child + .add(ValidationTriple.INHER_ID5.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID6.getTriple(), false) // different schema + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // different schema + .validate(handler); } @Test @@ -653,19 +705,20 @@ void testTableInheritanceRetrieveDataWithTableGrandchildTypeA() throws IOExcepti // All subjects still use Root IRIs but offers a way to "filter out parent triples". var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest, "GrandchildTypeA"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // grandparent of selected table - Map.entry(ValidationTriple.ID2, false), // parent of selected table - Map.entry(ValidationTriple.ID3, true), - Map.entry(ValidationTriple.ID4, false), // sibling of selected table - Map.entry(ValidationTriple.ID4_PARENT_FIELD, false), // sibling of selected table - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, false), // sibling of selected table - Map.entry(ValidationTriple.ID5, false), // different schema - Map.entry(ValidationTriple.ID6, false), // different schema - Map.entry(ValidationTriple.UNRELATED, false) // different schema - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // grandparent of selected table + .add(ValidationTriple.INHER_ID2.getTriple(), false) // parent of selected table + .add(ValidationTriple.INHER_ID3.getTriple(), true) + .add(ValidationTriple.INHER_ID4.getTriple(), false) // sibling of selected table + .add( + ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), false) // sibling of selected table + .add( + ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), + false) // sibling of selected table + .add(ValidationTriple.INHER_ID5.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID6.getTriple(), false) // different schema + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // different schema + .validate(handler); } @Test @@ -673,56 +726,51 @@ void testTableInheritanceRetrieveDataWithTableGrandchildTypeB() throws IOExcepti // All subjects still use Root IRIs but offers a way to "filter out parent triples". var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest, "GrandchildTypeB"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // grandparent of selected table - Map.entry(ValidationTriple.ID2, false), // parent of selected table - Map.entry(ValidationTriple.ID3, false), // sibling of selected table - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, false), // different schema - Map.entry(ValidationTriple.ID6, false), // different schema - Map.entry(ValidationTriple.UNRELATED, false) // different schema - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // grandparent of selected table + .add(ValidationTriple.INHER_ID2.getTriple(), false) // parent of selected table + .add(ValidationTriple.INHER_ID3.getTriple(), false) // sibling of selected table + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID6.getTriple(), false) // different schema + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // different schema + .validate(handler); } @Test void testTableInheritanceRetrieveDataWithRowId() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.ofRow(tableInherTest, "Root", "id=4"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // not selected - Map.entry(ValidationTriple.ID2, false), // not selected - Map.entry(ValidationTriple.ID3, false), // not selected - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, false), // not selected - Map.entry(ValidationTriple.ID6, false), // not selected - Map.entry(ValidationTriple.UNRELATED, false) // not selected - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID2.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID3.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID6.getTriple(), false) // not selected + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not selected + .validate(handler); } @Test void testTableInheritanceExternalSchemaRetrieveData() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherExtTest), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // different schema - Map.entry(ValidationTriple.ID2, false), // different schema - Map.entry(ValidationTriple.ID3, false), // different schema - Map.entry(ValidationTriple.ID4, false), // different schema - Map.entry(ValidationTriple.ID4_PARENT_FIELD, false), // different schema - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, false), // different schema - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, true), - Map.entry(ValidationTriple.UNRELATED, true))); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID2.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID3.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), true) + .add(ValidationTriple.INHER_UNRELATED.getTriple(), true) + .validate(handler); } @Test @@ -732,18 +780,17 @@ void testTableInheritanceExternalSchemaDataWithTableExternalChild() throws IOExc // `tableInherExtTest.getTable("Root")` == `null` var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherExtTest, "ExternalChild"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // different schema - Map.entry(ValidationTriple.ID2, false), // different schema - Map.entry(ValidationTriple.ID3, false), // different schema - Map.entry(ValidationTriple.ID4, false), // different schema - Map.entry(ValidationTriple.ID4_PARENT_FIELD, false), // different schema - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, false), // different schema - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, true), - Map.entry(ValidationTriple.UNRELATED, false))); // not part of inheritance + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID2.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID3.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), false) // different schema + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), true) + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not part of inheritance + .validate(handler); } @Test @@ -753,37 +800,34 @@ void testTableInheritanceExternalSchemaDataWithRowId() throws IOException { // `tableInherExtTest.getTable("Root")` == `null` var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.ofRow(tableInherExtTest, "ExternalChild", "id=5"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // not selected - Map.entry(ValidationTriple.ID2, false), // not selected - Map.entry(ValidationTriple.ID3, false), // not selected - Map.entry(ValidationTriple.ID4, false), // not selected - Map.entry(ValidationTriple.ID4_PARENT_FIELD, false), // not selected - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, false), // not selected - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, false), // not selected - Map.entry(ValidationTriple.UNRELATED, false) // not selected - )); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID2.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID3.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), false) // not selected + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not selected + .validate(handler); } @Test void testTableInheritanceRetrieveDataMultiSchema() throws IOException { var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(tableInherTest, tableInherExtTest), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, true), - Map.entry(ValidationTriple.ID2, true), - Map.entry(ValidationTriple.ID3, true), - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, true), - Map.entry(ValidationTriple.UNRELATED, true))); + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), true) + .add(ValidationTriple.INHER_ID2.getTriple(), true) + .add(ValidationTriple.INHER_ID3.getTriple(), true) + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), true) + .add(ValidationTriple.INHER_UNRELATED.getTriple(), true) + .validate(handler); } @Test @@ -793,18 +837,17 @@ void testTableInheritanceRetrieveDataMultiSchemaWithTableRoot() throws IOExcepti Selection.of( new Schema[] {tableInherTest, tableInherExtTest}, tableInherTest.getTable("Root")), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, true), - Map.entry(ValidationTriple.ID2, true), - Map.entry(ValidationTriple.ID3, true), - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, true), - Map.entry(ValidationTriple.UNRELATED, false))); // not part of inheritance + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), true) + .add(ValidationTriple.INHER_ID2.getTriple(), true) + .add(ValidationTriple.INHER_ID3.getTriple(), true) + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), true) + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not part of inheritance + .validate(handler); } @Test @@ -816,18 +859,17 @@ void testTableInheritanceRetrieveDataMultiSchemaWithRowId() throws IOException { tableInherTest.getTable("Root"), "id=4"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // not selected - Map.entry(ValidationTriple.ID2, false), // not selected - Map.entry(ValidationTriple.ID3, false), // not selected - Map.entry(ValidationTriple.ID4, true), - Map.entry(ValidationTriple.ID4_PARENT_FIELD, true), - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, true), - Map.entry(ValidationTriple.ID5, false), // not selected - Map.entry(ValidationTriple.ID6, false), // not selected - Map.entry(ValidationTriple.UNRELATED, false))); // not selected + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID2.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID3.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4.getTriple(), true) + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), true) + .add(ValidationTriple.INHER_ID5.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID6.getTriple(), false) // not selected + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not selected + .validate(handler); } @Test @@ -839,18 +881,17 @@ void testTableInheritanceRetrieveDataMultiSchemaWithExternalRowId() throws IOExc tableInherTest.getTable("Root"), "id=5"), handler); - assertPresence( - handler, - Map.ofEntries( - Map.entry(ValidationTriple.ID1, false), // not selected - Map.entry(ValidationTriple.ID2, false), // not selected - Map.entry(ValidationTriple.ID3, false), // not selected - Map.entry(ValidationTriple.ID4, false), // not selected - Map.entry(ValidationTriple.ID4_PARENT_FIELD, false), // not selected - Map.entry(ValidationTriple.ID4_GRANDPARENT_FIELD, false), // not selected - Map.entry(ValidationTriple.ID5, true), - Map.entry(ValidationTriple.ID6, false), // not selected - Map.entry(ValidationTriple.UNRELATED, false))); // not selected + new RdfValidator() + .add(ValidationTriple.INHER_ID1.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID2.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID3.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4_PARENT_FIELD.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID4_GRANDPARENT_FIELD.getTriple(), false) // not selected + .add(ValidationTriple.INHER_ID5.getTriple(), true) + .add(ValidationTriple.INHER_ID6.getTriple(), false) // not selected + .add(ValidationTriple.INHER_UNRELATED.getTriple(), false) // not selected + .validate(handler); } @Test @@ -909,12 +950,8 @@ void testSubClassesForInheritedTable() throws IOException { Table child = schema.create(table("child", column("name")).setInheritName("root")); var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(schema, child.getName()), handler); - var rootIRI = - Values.iri( - "http://localhost:8080/" + schema.getName() + "/api/rdf/" + root.getIdentifier()); - var childIRI = - Values.iri( - "http://localhost:8080/" + schema.getName() + "/api/rdf/" + child.getIdentifier()); + var rootIRI = Values.iri(getApi(schema) + root.getIdentifier()); + var childIRI = Values.iri(getApi(schema) + child.getIdentifier()); var cubeDataSetIRI = Values.iri("http://purl.org/linked-data/cube#DataSet"); var subclasses = handler.resources.get(childIRI).get(RDFS.SUBCLASSOF); assertEquals( @@ -939,12 +976,8 @@ void testSubClassRootTables() throws IOException { Table child = schema.create(table("child", column("name")).setInheritName("root")); var handler = new InMemoryRDFHandler() {}; getAndParseRDF(Selection.of(schema, root.getName()), handler); - var rootIRI = - Values.iri( - "http://localhost:8080/" + schema.getName() + "/api/rdf/" + root.getIdentifier()); - var childIRI = - Values.iri( - "http://localhost:8080/" + schema.getName() + "/api/rdf/" + child.getIdentifier()); + var rootIRI = Values.iri(getApi(schema) + root.getIdentifier()); + var childIRI = Values.iri(getApi(schema) + child.getIdentifier()); var cubeDataSetIRI = Values.iri("http://purl.org/linked-data/cube#DataSet"); var subclasses = handler.resources.get(rootIRI).get(RDFS.SUBCLASSOF); assertEquals( @@ -967,9 +1000,7 @@ void testCustomRdfSetting() throws IOException { final Set defaultNamespaces = new HashSet<>() { { - add( - new SimpleNamespace( - "CustomRdfEdit", "http://localhost:8080/CustomRdfEdit/api/rdf/")); + add(new SimpleNamespace("CustomRdfEdit", BASE_URL + "CustomRdfEdit/api/rdf/")); addAll(DEFAULT_NAMESPACES); } }; @@ -977,9 +1008,7 @@ void testCustomRdfSetting() throws IOException { final Set customNamespaces = new HashSet<>() { { - add( - new SimpleNamespace( - "CustomRdfEdit", "http://localhost:8080/CustomRdfEdit/api/rdf/")); + add(new SimpleNamespace("CustomRdfEdit", BASE_URL + "CustomRdfEdit/api/rdf/")); add(new SimpleNamespace("dcterms", "http://purl.org/dc/terms/")); } }; @@ -1038,8 +1067,8 @@ void testDuplicateNamespaces() throws IOException { final Set expectedNamespace = new HashSet<>() { { - add(new SimpleNamespace("RdfEqual1", "http://localhost:8080/RdfEqual1/api/rdf/")); - add(new SimpleNamespace("RdfEqual2", "http://localhost:8080/RdfEqual2/api/rdf/")); + add(new SimpleNamespace("RdfEqual1", BASE_URL + "RdfEqual1/api/rdf/")); + add(new SimpleNamespace("RdfEqual2", BASE_URL + "RdfEqual2/api/rdf/")); add(new SimpleNamespace("dcterms", "http://purl.org/dc/terms/")); } }; @@ -1068,8 +1097,8 @@ void testNamespaceDifferentPrefixSameUrl() throws IOException { final Set expectedNamespace = new HashSet<>() { { - add(new SimpleNamespace("RdfPrefix1", "http://localhost:8080/RdfPrefix1/api/rdf/")); - add(new SimpleNamespace("RdfPrefix2", "http://localhost:8080/RdfPrefix2/api/rdf/")); + add(new SimpleNamespace("RdfPrefix1", BASE_URL + "RdfPrefix1/api/rdf/")); + add(new SimpleNamespace("RdfPrefix2", BASE_URL + "RdfPrefix2/api/rdf/")); add(new SimpleNamespace("dcterms1", "http://purl.org/dc/terms/")); } }; @@ -1113,12 +1142,8 @@ void testNamespaceDifferentUrlSamePrefix() throws IOException { final Set expectedNamespace = new HashSet<>() { { - add( - new SimpleNamespace( - "RdfPrefixUrl1", "http://localhost:8080/RdfPrefixUrl1/api/rdf/")); - add( - new SimpleNamespace( - "RdfPrefixUrl2", "http://localhost:8080/RdfPrefixUrl2/api/rdf/")); + add(new SimpleNamespace("RdfPrefixUrl1", BASE_URL + "RdfPrefixUrl1/api/rdf/")); + add(new SimpleNamespace("RdfPrefixUrl2", BASE_URL + "RdfPrefixUrl2/api/rdf/")); add(new SimpleNamespace("name", "http://www.w3.org/2000/01/rdf-schema#")); } }; @@ -1157,12 +1182,8 @@ void testPartlyCustomRdf() throws IOException { final Set expectedNamespaces = new HashSet<>() { { - add( - new SimpleNamespace( - "RdfPartlyCustom1", "http://localhost:8080/RdfPartlyCustom1/api/rdf/")); - add( - new SimpleNamespace( - "RdfPartlyCustom2", "http://localhost:8080/RdfPartlyCustom2/api/rdf/")); + add(new SimpleNamespace("RdfPartlyCustom1", BASE_URL + "RdfPartlyCustom1/api/rdf/")); + add(new SimpleNamespace("RdfPartlyCustom2", BASE_URL + "RdfPartlyCustom2/api/rdf/")); add(new SimpleNamespace("ncit", "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl#")); addAll(DEFAULT_NAMESPACES); } @@ -1182,12 +1203,8 @@ void testCustomOrEmptyRdf() throws IOException { final Set expectedNamespaces = new HashSet<>() { { - add( - new SimpleNamespace( - "RdfcustomOrEmpty1", "http://localhost:8080/RdfcustomOrEmpty1/api/rdf/")); - add( - new SimpleNamespace( - "RdfcustomOrEmpty2", "http://localhost:8080/RdfcustomOrEmpty2/api/rdf/")); + add(new SimpleNamespace("RdfcustomOrEmpty1", BASE_URL + "RdfcustomOrEmpty1/api/rdf/")); + add(new SimpleNamespace("RdfcustomOrEmpty2", BASE_URL + "RdfcustomOrEmpty2/api/rdf/")); add(new SimpleNamespace("ncit", "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl#")); } }; @@ -1211,8 +1228,8 @@ void testFileMetadataTriples() throws IOException { Set files = handler .resources - .get(Values.iri("http://localhost:8080/fileTest/api/rdf/MyFiles?id=1")) - .get(Values.iri("http://localhost:8080/fileTest/api/rdf/MyFiles/column/file")); + .get(Values.iri(getApi(fileTest) + "MyFiles?id=1")) + .get(Values.iri(getApi(fileTest) + "MyFiles/column/file")); IRI fileIRI = (IRI) files.stream().findFirst().get(); @@ -1238,12 +1255,9 @@ void refBackInRdf() throws IOException { Set refBacks = handler .resources - .get(Values.iri("http://localhost:8080/refBackTest/api/rdf/TableRefBack?id=a")) - .get( - Values.iri( - "http://localhost:8080/refBackTest/api/rdf/TableRefBack/column/backlink")); - assertEquals( - Set.of(Values.iri("http://localhost:8080/refBackTest/api/rdf/TableRef?id=1")), refBacks); + .get(Values.iri(getApi(refBackTest) + "TableRefBack?id=a")) + .get(Values.iri(getApi(refBackTest) + "TableRefBack/column/backlink")); + assertEquals(Set.of(Values.iri(getApi(refBackTest) + "TableRef?id=1")), refBacks); } /** @@ -1336,103 +1350,117 @@ static Selection ofRow(Schema[] schema, Table table, String rowId) { } } - void assertPresence(InMemoryRDFHandler handler, Map presenceMap) { - // Tracks errors. - List errors = new ArrayList<>(); - - for (ValidationTriple triple : presenceMap.keySet()) { - Map> predicates = handler.resources.get(triple.getSubject()); - // If Triple should be present in handler. - if (presenceMap.get(triple)) { - if (predicates == null) { - errors.add("Missing predicates for subject: " + triple.getSubject()); - continue; - } - Set objects = predicates.get(triple.getPredicate()); - if (objects == null) { - errors.add("Missing objects for predicate: " + triple.getPredicate()); - continue; - } - if (objects.size() != 1) { - errors.add("Only 1 object should be present for: " + triple.getPredicate()); - } - Object firstObject = objects.toArray()[0]; - if (!triple.getObject().equals(firstObject)) { - errors.add( - "First object not equal to expected value. Found \"" - + firstObject - + "\", but should be \"" - + triple.getObject() - + "\""); - } - } // If Triple should not be present in handler. - else { - if (predicates != null) - errors.add("Found predicates while expecting none for subject: " + triple.getSubject()); - } - } - - // Compares error ArrayList to empty one so actual messages are shown if any are found. - assertEquals(new ArrayList<>(), errors); - } - private enum ValidationTriple { - ID1( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=1", - "http://localhost:8080/tableInheritanceTest/api/rdf/Root/column/rootColumn", - "id1 data"), - ID2( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=2", - "http://localhost:8080/tableInheritanceTest/api/rdf/Child/column/childColumn", - "id2 data"), - ID3( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=3", - "http://localhost:8080/tableInheritanceTest/api/rdf/GrandchildTypeA/column/grandchildColumn", - "id3 data"), - ID4( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=4", - "http://localhost:8080/tableInheritanceTest/api/rdf/GrandchildTypeB/column/grandchildColumn", - "id4 data"), - ID4_GRANDPARENT_FIELD( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=4", - "http://localhost:8080/tableInheritanceTest/api/rdf/Root/column/rootColumn", - "id4 data for rootColumn"), - ID4_PARENT_FIELD( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=4", - "http://localhost:8080/tableInheritanceTest/api/rdf/Child/column/childColumn", - "id4 data for childColumn"), - ID5( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=5", - "http://localhost:8080/tableInheritanceExternalSchemaTest/api/rdf/ExternalChild/column/externalChildColumn", - "id5 data"), - ID6( - "http://localhost:8080/tableInheritanceTest/api/rdf/Root?id=6", - "http://localhost:8080/tableInheritanceExternalSchemaTest/api/rdf/ExternalGrandchild/column/externalGrandchildColumn", - "id6 data"), - UNRELATED( - "http://localhost:8080/tableInheritanceExternalSchemaTest/api/rdf/ExternalUnrelated?id=a", - "http://localhost:8080/tableInheritanceExternalSchemaTest/api/rdf/ExternalUnrelated/column/externalUnrelatedColumn", - "unrelated data"); + // Inheritance testing + INHER_ID1( + getApi(tableInherTest) + "Root?id=1", + getApi(tableInherTest) + "Root/column/rootColumn", + Values.literal("id1 data")), + INHER_ID2( + getApi(tableInherTest) + "Root?id=2", + getApi(tableInherTest) + "Child/column/childColumn", + Values.literal("id2 data")), + INHER_ID3( + getApi(tableInherTest) + "Root?id=3", + getApi(tableInherTest) + "GrandchildTypeA/column/grandchildColumn", + Values.literal("id3 data")), + INHER_ID4( + getApi(tableInherTest) + "Root?id=4", + getApi(tableInherTest) + "GrandchildTypeB/column/grandchildColumn", + Values.literal("id4 data")), + INHER_ID4_GRANDPARENT_FIELD( + getApi(tableInherTest) + "Root?id=4", + getApi(tableInherTest) + "Root/column/rootColumn", + Values.literal("id4 data for rootColumn")), + INHER_ID4_PARENT_FIELD( + getApi(tableInherTest) + "Root?id=4", + getApi(tableInherTest) + "Child/column/childColumn", + Values.literal("id4 data for childColumn")), + INHER_ID5( + getApi(tableInherTest) + "Root?id=5", + getApi(tableInherExtTest) + "ExternalChild/column/externalChildColumn", + Values.literal("id5 data")), + INHER_ID6( + getApi(tableInherTest) + "Root?id=6", + getApi(tableInherExtTest) + "ExternalGrandchild/column/externalGrandchildColumn", + Values.literal("id6 data")), + INHER_UNRELATED( + getApi(tableInherExtTest) + "ExternalUnrelated?id=a", + getApi(tableInherExtTest) + "ExternalUnrelated/column/externalUnrelatedColumn", + Values.literal("unrelated data")), + + // Composite testing + COMP_ROOT1_KEY_REF( + COMP_ROOT1_FIRST.get(), + getApi(compositeKeyTest) + "Root1/column/r1", + COMP_CHILD1_FIRST.get()), + COMP_ROOT2_KEY_REF( + COMP_ROOT2_FIRST.get(), + getApi(compositeKeyTest) + "Root2/column/r2b", + COMP_CHILD1_SECOND.get()), + COMP_CHILD1_FIRST_KEY_REF( + COMP_CHILD1_FIRST.get(), + getApi(compositeKeyTest) + "Child1/column/c1b", + COMP_GRANDCHILD1_FIRST.get()), + COMP_CHILD1_FIRST_REFBACK_ROOT1( + COMP_CHILD1_FIRST.get(), + getApi(compositeKeyTest) + "Child1/column/root1refback", + COMP_ROOT1_FIRST.get()), + COMP_CHILD1_SECOND_KEY_REF( + COMP_CHILD1_SECOND.get(), + getApi(compositeKeyTest) + "Child1/column/c1b", + COMP_GRANDCHILD1_FIRST.get()), + COMP_CHILD1_SECOND_REFBACK_ROOT2( + COMP_CHILD1_SECOND.get(), + getApi(compositeKeyTest) + "Child1/column/root2refback", + COMP_ROOT2_FIRST.get()), + COMP_CHILD1_SECOND_NON_KEY_REF( + COMP_CHILD1_SECOND.get(), + getApi(compositeKeyTest) + "Child1/column/grandchild1ref", + COMP_GRANDCHILD1_SECOND.get()), + COMP_GRANDCHILD_REFBACK_1( + COMP_GRANDCHILD1_FIRST.get(), + getApi(compositeKeyTest) + "Grandchild1/column/child1refback", + COMP_CHILD1_FIRST.get()), + COMP_GRANDCHILD_REFBACK_2( + COMP_GRANDCHILD1_FIRST.get(), + getApi(compositeKeyTest) + "Grandchild1/column/child1refback", + COMP_CHILD1_FIRST.get()); private final Triple triple; - public Resource getSubject() { - return triple.getSubject(); + public Triple getTriple() { + return triple; } - public IRI getPredicate() { - return triple.getPredicate(); + ValidationTriple(String subjectUrl, String predicateUrl, Value object) { + this(Values.iri(subjectUrl), predicateUrl, object); } - public Value getObject() { - return triple.getObject(); - } - - ValidationTriple(String subjectUrl, String predicateUrl, String objectString) { + ValidationTriple(Value subject, String predicateUrl, Value object) { this.triple = SimpleValueFactory.getInstance() - .createTriple( - Values.iri(subjectUrl), Values.iri(predicateUrl), Values.literal(objectString)); + .createTriple((Resource) subject, Values.iri(predicateUrl), object); + } + } + + enum ValidationSubjects { + COMP_ROOT1_FIRST("Root1?r1.c1a=c1a_first&r1.c1b.gc1a=gc1a_first&r1.c1b.gc1b=gc1b_first"), + COMP_ROOT2_FIRST( + "Root2?r2a=r2a_first&r2b.c1a=c1a_second&r2b.c1b.gc1a=gc1a_first&r2b.c1b.gc1b=gc1b_first"), + COMP_CHILD1_FIRST("Child1?c1a=c1a_first&c1b.gc1a=gc1a_first&c1b.gc1b=gc1b_first"), + COMP_CHILD1_SECOND("Child1?c1a=c1a_second&c1b.gc1a=gc1a_first&c1b.gc1b=gc1b_first"), + COMP_GRANDCHILD1_FIRST("Grandchild1?gc1a=gc1a_first&gc1b=gc1b_first"), + COMP_GRANDCHILD1_SECOND("Grandchild1?gc1a=gc1a_second&gc1b=gc1b_second"); + + Value value; + + public Value get() { + return value; + } + + ValidationSubjects(String resource) { + this.value = Values.iri(getApi(compositeKeyTest) + resource); } } } diff --git a/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RdfValidator.java b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RdfValidator.java new file mode 100644 index 0000000000..52002a2472 --- /dev/null +++ b/backend/molgenis-emx2-rdf/src/test/java/org/molgenis/emx2/rdf/RdfValidator.java @@ -0,0 +1,93 @@ +package org.molgenis.emx2.rdf; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Triple; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.util.Values; + +public class RdfValidator { + public Map>> validations = new HashMap<>(); + + public RdfValidator add(Triple triple, boolean isPresent) { + return add(triple.getSubject(), triple.getPredicate(), triple.getObject(), isPresent); + } + + public RdfValidator add(String subject, String predicate, String object, boolean isPresent) { + return add(subject, predicate, object, false, isPresent); + } + + public RdfValidator add( + String subject, String predicate, String object, boolean objectIsIRI, boolean isPresent) { + Value objectValue = (objectIsIRI ? Values.iri(object) : Values.literal(object)); + return add((Resource) Values.iri(subject), (IRI) Values.iri(predicate), objectValue, isPresent); + } + + public RdfValidator add(Resource subject, IRI predicate, Value object, boolean isPresent) { + validations.putIfAbsent(subject, new HashMap<>()); + Map> predicates = validations.get(subject); + predicates.putIfAbsent(predicate, new HashMap<>()); + Map subjects = predicates.get(predicate); + subjects.put(object, isPresent); + return this; + } + + public RdfValidator addAll(Collection triples, boolean isPresent) { + triples.forEach(i -> add(i, isPresent)); + return this; + } + + void validate(InMemoryRDFHandler handler) { + // Tracks errors. + List errors = new ArrayList<>(); + + // Compares expected with actual. + for (Map.Entry>> validationSubject : + validations.entrySet()) { + Map> foundPredicates = + handler.resources.getOrDefault(validationSubject.getKey(), null); + + for (Map.Entry> validationPredicate : + validationSubject.getValue().entrySet()) { + Set foundObjects; + if (foundPredicates != null) { + foundObjects = foundPredicates.getOrDefault(validationPredicate.getKey(), null); + } else { + foundObjects = null; + } + for (Map.Entry validationObject : + validationPredicate.getValue().entrySet()) { + if (validationObject.getValue() + && (foundObjects == null || !foundObjects.contains(validationObject.getKey()))) { + errors.add( + String.format( + "Triple <<%s %s %s>> is expected but not found", + validationSubject.getKey(), + validationPredicate.getKey(), + validationObject.getKey())); + } else if (!validationObject.getValue() + && foundObjects != null + && foundObjects.contains(validationObject.getKey())) { + errors.add( + String.format( + "Triple <<%s %s %s>> is found while it should not be present", + validationSubject.getKey(), + validationPredicate.getKey(), + validationObject.getKey())); + } + } + } + } + + // Compares error ArrayList to empty one so actual messages are shown if any are found. + assertEquals(new ArrayList<>(), errors); + } +} diff --git a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java index 845522b3ac..4cf27d52d4 100644 --- a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java +++ b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlQuery.java @@ -63,7 +63,7 @@ public SqlQuery(SqlSchemaMetadata schema, String field, SelectColumn[] selection /** Create alias that is short enough for postgresql to not complain */ public String alias(String label) { - if (!label.contains("-")) { + if (!label.contains(SUBSELECT_SEPARATOR)) { // we only need aliases for subquery tables return label; } diff --git a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Constants.java b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Constants.java index 2ec3e4773d..c07b4282dc 100644 --- a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Constants.java +++ b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Constants.java @@ -10,6 +10,7 @@ public class Constants { public static final String MG_USER_PREFIX = "MG_USER_"; public static final String COMPOSITE_REF_SEPARATOR = "."; + public static final String SUBSELECT_SEPARATOR = "-"; public static final String REF_LINK = "refLink"; public static final String REF_LABEL = "refLabel"; public static final String REF_LABEL_DEFAULT = "refLabelDefault";