Skip to content

Commit b7c8bb2

Browse files
committed
fix: Fixed SPARQLRelationFetcher when the type has no property
OpenSILEX/opensilex-dev!1543
1 parent 4d68eba commit b7c8bb2

3 files changed

Lines changed: 228 additions & 2 deletions

File tree

opensilex-core/src/main/java/org/opensilex/core/ontology/dal/SPARQLRelationFetcher.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,12 @@ public void updateModels() throws SPARQLException, ParseException {
277277
}
278278

279279
protected void updateMonoValued(SPARQLResult result, T initialModel) {
280-
281280
List<URI> typeProperties = this.monoValuedPropertiesByType.get(initialModel.getType());
281+
282+
if (typeProperties == null) {
283+
return;
284+
}
285+
282286
List<String> propertiesNames = this.monoValuedPropertiesByTypeVarNames.get(initialModel.getType());
283287

284288
for (int i = 0; i < typeProperties.size(); i++) {
@@ -294,8 +298,12 @@ protected void updateMonoValued(SPARQLResult result, T initialModel) {
294298
}
295299

296300
protected void updateMultiValued(SPARQLResult result, T initialModel) {
297-
298301
List<URI> typeProperties = this.multiValuedPropertiesByType.get(initialModel.getType());
302+
303+
if (typeProperties == null) {
304+
return;
305+
}
306+
299307
List<String> propertiesNames = this.multiValuedPropertiesByTypeVarNames.get(initialModel.getType());
300308

301309
for (int i = 0; i < typeProperties.size(); i++) {
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package org.opensilex.core.ontology.bll;
2+
3+
import org.apache.jena.arq.querybuilder.SelectBuilder;
4+
import org.apache.jena.arq.querybuilder.WhereBuilder;
5+
import org.apache.jena.riot.Lang;
6+
import org.apache.jena.vocabulary.RDF;
7+
import org.apache.jena.vocabulary.RDFS;
8+
import org.junit.BeforeClass;
9+
import org.junit.Test;
10+
import org.opensilex.OpenSilex;
11+
import org.opensilex.core.AbstractMongoIntegrationTest;
12+
import org.opensilex.core.device.dal.DeviceModel;
13+
import org.opensilex.core.experiment.dal.ExperimentModel;
14+
import org.opensilex.core.ontology.Oeso;
15+
import org.opensilex.core.ontology.dal.SPARQLRelationFetcher;
16+
import org.opensilex.core.scientificObject.dal.ScientificObjectModel;
17+
import org.opensilex.sparql.exceptions.SPARQLException;
18+
import org.opensilex.sparql.model.SPARQLModelRelation;
19+
import org.opensilex.sparql.service.SPARQLService;
20+
import org.opensilex.sparql.utils.Ontology;
21+
22+
import java.net.URI;
23+
import java.net.URISyntaxException;
24+
import java.nio.file.Path;
25+
import java.nio.file.Paths;
26+
import java.time.LocalDate;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Set;
31+
32+
import static java.util.stream.Collectors.toSet;
33+
import static org.apache.commons.collections4.CollectionUtils.isEqualCollection;
34+
import static org.junit.Assert.assertEquals;
35+
import static org.junit.Assert.assertTrue;
36+
import static org.opensilex.sparql.deserializer.SPARQLDeserializers.getExpandedURI;
37+
import static org.opensilex.sparql.deserializer.SPARQLDeserializers.nodeURI;
38+
import static org.opensilex.sparql.service.SPARQLQueryHelper.makeVar;
39+
40+
public class SPARQLRelationFetcherTest extends AbstractMongoIntegrationTest {
41+
private static SPARQLService sparql;
42+
43+
private static final Path ONTOLOGY_PATH = Paths.get("ontologies", "sparqlRelationFetcherTest.owl");
44+
private static final URI ONTOLOGY_URI = URI.create("http://example.org/opensilex/test/ontology/sparql-relation-fetcher#");
45+
private static final URI PROP_1_URI = URI.create(ONTOLOGY_URI + "prop1");
46+
private static final URI TYPE_1_URI = URI.create(ONTOLOGY_URI + "type1");
47+
private static final URI TYPE_2_URI = URI.create(ONTOLOGY_URI + "type2");
48+
49+
@BeforeClass
50+
public static void beforeTest() throws URISyntaxException, SPARQLException {
51+
sparql = newSparqlService();
52+
}
53+
54+
private static ExperimentModel makeExperiment(URI experimentUri, String name) throws Exception {
55+
var experiment = new ExperimentModel();
56+
experiment.setName(name);
57+
experiment.setObjective(name + " objective");
58+
experiment.setUri(experimentUri);
59+
experiment.setStartDate(LocalDate.parse("2026-09-01"));
60+
return experiment;
61+
}
62+
63+
private static DeviceModel makeDevice(URI deviceURI, String name) {
64+
var device = new DeviceModel();
65+
device.setName(name);
66+
device.setUri(deviceURI);
67+
device.setType(URI.create(Oeso.Device.getURI()));
68+
return device;
69+
}
70+
71+
private static SPARQLModelRelation makeObjectRelation(URI property, URI object) {
72+
var relation = new SPARQLModelRelation();
73+
relation.setProperty(Ontology.property(property));
74+
relation.setValue(object.toString());
75+
relation.setType(URI.class);
76+
return relation;
77+
}
78+
79+
private static ScientificObjectModel makeScientificObject(URI uri, String name, URI type, Map<URI, List<URI>> objectRelations) {
80+
var so = new ScientificObjectModel();
81+
so.setName(name);
82+
so.setUri(uri);
83+
so.setType(type);
84+
so.setRelations(objectRelations.entrySet().stream()
85+
.flatMap(entry -> entry.getValue().stream().map(value -> makeObjectRelation(entry.getKey(), value)))
86+
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll));
87+
return so;
88+
}
89+
90+
private static SelectBuilder makeObjectSelect(URI experiment) {
91+
var rdfType = makeVar("rdfType");
92+
var uri = makeVar("uri");
93+
var name = makeVar("name");
94+
var experimentNode = nodeURI(experiment);
95+
96+
return new SelectBuilder()
97+
.addVar(rdfType)
98+
.addVar(uri)
99+
.addVar(name)
100+
.addWhere(rdfType, Ontology.subClassAny, Oeso.ScientificObject)
101+
.addGraph(experimentNode, new WhereBuilder()
102+
.addWhere(uri, RDFS.label, name)
103+
.addWhere(uri, RDF.type, rdfType));
104+
}
105+
106+
private static URI testUri(String suffix) {
107+
return URI.create("http://example.org/opensilex/test/" + suffix);
108+
}
109+
110+
/**
111+
* The SPARQLRelationFetcher should not throw when fetching mixed mono- and multivalued properties. This test
112+
* follows this setup:
113+
*
114+
* <ul>
115+
* <li>Create an object property on Scientific Objects (prop1)</li>
116+
* <li>Create two Scientific Object types (type1 and type2)</li>
117+
* <li>Add a <strong>multivalued</strong> restriction in type1 on prop1</li>
118+
* <li>Add a <strong>monovalued</strong> restriction in type2 on prop1</li>
119+
* <li>Create an experiment</li>
120+
* <li>Create two Scientific Objects of types type1 and type2 in the experiment</li>
121+
* <li>Set values for the relation prop1 in both Scientific Objects</li>
122+
* </ul>
123+
*
124+
* This specific combination previously failed with a NullPointerException when trying to populate the models with
125+
* the fetched relations. That was because the type <code>type2</code> is not associated with any multivalued
126+
* restriction, but the code to associate the multivalued relations is still executed because <code>type1</code> has
127+
* a multivalued restriction on <code>prop1</code>.
128+
*/
129+
@Test
130+
public void testMixedMultiMonoValuedProperty() throws Exception {
131+
sparql.loadOntology(ONTOLOGY_URI, OpenSilex.getResourceAsStream(ONTOLOGY_PATH.toString()), Lang.RDFXML);
132+
133+
var experimentUri = testUri("experiment/1");
134+
var experimentNode = nodeURI(experimentUri);
135+
var device1Uri = testUri("device/1");
136+
var device2Uri = testUri("device/2");
137+
var object1Uri = testUri("object/1");
138+
var object2Uri = testUri("object/2");
139+
sparql.create(makeExperiment(experimentUri, "experiment"));
140+
sparql.create(makeDevice(device1Uri, "device1"));
141+
sparql.create(makeDevice(device2Uri, "device2"));
142+
sparql.create(experimentNode, makeScientificObject(object1Uri, "object1", TYPE_1_URI, Map.of(PROP_1_URI, List.of(device1Uri, device2Uri))));
143+
sparql.create(experimentNode, makeScientificObject(object2Uri, "object2", TYPE_2_URI, Map.of(PROP_1_URI, List.of(device1Uri))));
144+
145+
var object1 = makeScientificObject(object1Uri, "object1", TYPE_1_URI, Map.of());
146+
var object2 = makeScientificObject(object2Uri, "object2", TYPE_2_URI, Map.of());
147+
var initialModels = List.of(object1, object2);
148+
var select = makeObjectSelect(experimentUri);
149+
var relationFetcher = new SPARQLRelationFetcher<>(
150+
sparql,
151+
ScientificObjectModel.class,
152+
experimentNode,
153+
select,
154+
initialModels);
155+
relationFetcher.updateModels();
156+
157+
var rels1 = object1.getRelations();
158+
var relProperties1 = rels1.stream().map(rel -> getExpandedURI(rel.getProperty().getURI())).collect(toSet());
159+
var relValues1 = rels1.stream().map(rel -> getExpandedURI(rel.getValue())).collect(toSet());
160+
var rels2 = object2.getRelations();
161+
var relProperties2 = rels2.stream().map(rel -> getExpandedURI(rel.getProperty().getURI())).collect(toSet());
162+
var relValues2 = rels2.stream().map(rel -> getExpandedURI(rel.getValue())).collect(toSet());
163+
164+
assertEquals(2, rels1.size());
165+
assertTrue(isEqualCollection(Set.of(PROP_1_URI.toString()), relProperties1));
166+
assertTrue(isEqualCollection(Set.of(device1Uri.toString(), device2Uri.toString()), relValues1));
167+
assertEquals(1, rels2.size());
168+
assertTrue(isEqualCollection(Set.of(PROP_1_URI.toString()), relProperties2));
169+
assertTrue(isEqualCollection(Set.of(device1Uri.toString()), relValues2));
170+
}
171+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0"?>
2+
<rdf:RDF
3+
xmlns="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#"
4+
xmlns:dc="http://purl.org/dc/elements/1.1/"
5+
xmlns:owl="http://www.w3.org/2002/07/owl#"
6+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7+
xmlns:xml="http://www.w3.org/XML/1998/namespace"
8+
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
9+
xmlns:oeso="http://www.opensilex.org/vocabulary/oeso#"
10+
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
11+
<owl:Ontology rdf:about="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#">
12+
<dc:creator>Valentin Rigolle</dc:creator>
13+
<rdfs:label xml:lang="en">Test ontology for SPARQLRelationFetcher</rdfs:label>
14+
</owl:Ontology>
15+
16+
<owl:ObjectProperty rdf:about="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#prop1">
17+
<rdfs:subPropertyOf rdf:resource="http://www.w3.org/2002/07/owl#topObjectProperty"/>
18+
<rdfs:domain rdf:resource="http://www.opensilex.org/vocabulary/oeso#ScientificObject"/>
19+
<rdfs:range rdf:resource="http://www.opensilex.org/vocabulary/oeso#Device"/>
20+
<rdfs:label xml:lang="en">SO prop 1</rdfs:label>
21+
</owl:ObjectProperty>
22+
23+
<owl:Class rdf:about="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#type1">
24+
<rdfs:subClassOf rdf:resource="http://www.opensilex.org/vocabulary/oeso#ScientificObject"/>
25+
<rdfs:label xml:lang="en">SO type 1</rdfs:label>
26+
<rdfs:subClassOf>
27+
<owl:Restriction>
28+
<owl:onProperty rdf:resource="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#prop1"/>
29+
<owl:onClass rdf:resource="http://www.opensilex.org/vocabulary/oeso#Device"/>
30+
<owl:minQualifiedCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">0</owl:minQualifiedCardinality>
31+
</owl:Restriction>
32+
</rdfs:subClassOf>
33+
</owl:Class>
34+
35+
<owl:Class rdf:about="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#type2">
36+
<rdfs:subClassOf rdf:resource="http://www.opensilex.org/vocabulary/oeso#ScientificObject"/>
37+
<rdfs:label xml:lang="en">SO type 2</rdfs:label>
38+
<rdfs:subClassOf>
39+
<owl:Restriction>
40+
<owl:onProperty rdf:resource="http://example.org/opensilex/test/ontology/sparql-relation-fetcher#prop1"/>
41+
<owl:onClass rdf:resource="http://www.opensilex.org/vocabulary/oeso#Device"/>
42+
<owl:minQualifiedCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">0</owl:minQualifiedCardinality>
43+
<owl:maxQualifiedCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxQualifiedCardinality>
44+
</owl:Restriction>
45+
</rdfs:subClassOf>
46+
</owl:Class>
47+
</rdf:RDF>

0 commit comments

Comments
 (0)