Skip to content

Commit

Permalink
Merge pull request #86 from opendata-mvcr/enhancement/85-subclassof-b…
Browse files Browse the repository at this point in the history
…roader

Enhancement/85 subclassof broader
  • Loading branch information
psiotwo authored May 12, 2021
2 parents cdf07d2 + 6351faa commit 885af15
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 19 deletions.
4 changes: 4 additions & 0 deletions ontology/termit-model.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,7 @@ termit-pojem:je-publikován

termit-pojem:má-můj-poslední-komentář
a owl:ObjectProperty.

rdfs:subClassOf
a rdf:Property ;
rdfs:subPropertyOf <http://www.w3.org/2004/02/skos/core#broader> .
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<repositories>
<repository>
<id>kbss</id>
<url>http://kbss.felk.cvut.cz/m2repo</url>
<url>https://kbss.felk.cvut.cz/m2repo</url>
</repository>
</repositories>

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/cz/cvut/kbss/termit/dto/TermDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import cz.cvut.kbss.termit.model.AbstractTerm;
import cz.cvut.kbss.termit.model.Term;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -56,4 +57,12 @@ public Set<TermDto> getParentTerms() {
public void setParentTerms(Set<TermDto> parentTerms) {
this.parentTerms = parentTerms;
}

public void addParentTerms(Collection<TermDto> parents) {
Objects.requireNonNull(parents);
if (parentTerms == null) {
this.parentTerms = new LinkedHashSet<>();
}
parentTerms.addAll(parents);
}
}
21 changes: 17 additions & 4 deletions src/main/java/cz/cvut/kbss/termit/model/Term.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import cz.cvut.kbss.jopa.model.annotations.*;
import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
import cz.cvut.kbss.jopa.vocabulary.DC;
import cz.cvut.kbss.jopa.vocabulary.RDFS;
import cz.cvut.kbss.jopa.vocabulary.SKOS;
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
import cz.cvut.kbss.termit.dto.TermInfo;
Expand Down Expand Up @@ -33,7 +34,7 @@
@Configurable
@Audited
@OWLClass(iri = SKOS.CONCEPT)
@JsonLdAttributeOrder({"uri", "label", "description", "subTerms"})
@JsonLdAttributeOrder({"uri", "label", "description", "parentTerms", "superTypes", "subTerms"})
public class Term extends AbstractTerm implements HasTypes {

/**
Expand All @@ -42,8 +43,7 @@ public class Term extends AbstractTerm implements HasTypes {
public static final List<String> EXPORT_COLUMNS = Collections
.unmodifiableList(
Arrays.asList("IRI", "Label", "Alternative Labels", "Hidden Labels", "Definition", "Description",
"Types", "Sources", "Parent term",
"SubTerms", "Draft"));
"Types", "Sources", "Parent term", "SubTerms", "Draft"));

@Autowired
@Transient
Expand All @@ -64,6 +64,10 @@ public class Term extends AbstractTerm implements HasTypes {
@OWLObjectProperty(iri = SKOS.BROADER, fetch = FetchType.EAGER)
private Set<Term> parentTerms;

@OWLObjectProperty(iri = RDFS.SUB_CLASS_OF, fetch = FetchType.EAGER)
// TODO Replace with TermInfo when new the new model with TermInfo being entity is merged from KBSS
private Set<Term> superTypes;

@Inferred
@OWLObjectProperty(iri = Vocabulary.s_p_ma_zdroj_definice_termu, fetch = FetchType.EAGER)
private TermDefinitionSource definitionSource;
Expand Down Expand Up @@ -144,6 +148,14 @@ public void addParentTerm(Term term) {
parentTerms.add(term);
}

public Set<Term> getSuperTypes() {
return superTypes;
}

public void setSuperTypes(Set<Term> superTypes) {
this.superTypes = superTypes;
}

public Set<String> getSources() {
return sources;
}
Expand Down Expand Up @@ -270,7 +282,8 @@ public void toExcel(Row row) {
* term at all
*/
public boolean hasParentInSameVocabulary() {
return parentTerms != null && parentTerms.stream().anyMatch(p -> p.getGlossary().equals(getGlossary()));
return parentTerms != null && parentTerms.stream().anyMatch(p -> p.getGlossary().equals(getGlossary()))
|| superTypes != null && superTypes.stream().anyMatch(p -> p.getGlossary().equals(getGlossary()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ public Descriptor termDescriptor(URI vocabularyUri) {
persistenceUtils.getCanonicalContainerContexts().forEach(parentDescriptor::addContext);
// Allow indefinite length of the ancestor chain
parentDescriptor.addAttributeDescriptor(fieldSpec(Term.class, "parentTerms"), parentDescriptor);
parentDescriptor.addAttributeDescriptor(fieldSpec(Term.class, "superTypes"), parentDescriptor);
descriptor.addAttributeDescriptor(fieldSpec(Term.class, "parentTerms"), parentDescriptor);
descriptor.addAttributeDescriptor(fieldSpec(Term.class, "superTypes"), parentDescriptor);
// Definition source is inferred. That means it is in a special context in GraphDB. Therefore, we need to use
// the default context to prevent JOPA from thinking the value has changed on merge
descriptor.addAttributeContext(fieldSpec(Term.class, "definitionSource"), null);
Expand Down
57 changes: 43 additions & 14 deletions src/main/java/cz/cvut/kbss/termit/persistence/dao/TermDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,14 @@ private <T extends AbstractTerm> List<T> findAllFrom(Set<URI> contexts, Pageable
final Descriptor descriptor = descriptorFactory.termDescriptor((URI) null);
resolveWorkspaceAndCanonicalContexts().forEach(descriptor::addContext);
query.setDescriptor(descriptor);
return executeQueryAndLoadSubTerms(query, contexts);
final List<T> result = executeQueryAndLoadSubTerms(query, contexts);
if (TermDto.class.isAssignableFrom(resultType)) {
result.forEach(t -> {
final TermDto dto = (TermDto) t;
dto.addParentTerms(loadInferredParentTerms(dto, contexts, dto.getParentTerms()));
});
}
return result;
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
Expand Down Expand Up @@ -409,12 +416,32 @@ private List<TermDto> findAllFrom(Set<URI> contexts, String searchString, Pageab
final Descriptor descriptor = descriptorFactory.termDescriptor((URI) null);
contexts.forEach(descriptor::addContext);
query.setDescriptor(descriptor);
return executeQueryAndLoadSubTerms(query, contexts);
final List<TermDto> result = executeQueryAndLoadSubTerms(query, contexts);
result.forEach(t -> {
t.addParentTerms(loadInferredParentTerms(t, contexts, t.getParentTerms()));
});
return result;
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
}

private List<TermDto> loadInferredParentTerms(TermDto term, Set<URI> graphs, Set<TermDto> exclude) {
return em.createNativeQuery("SELECT DISTINCT ?parent WHERE {" +
"GRAPH ?g { " +
"?parent a ?type ." +
"}" +
"?term ?broader ?parent ." + // Let broader be outside of the graph to include inference
"FILTER (?g IN (?graphs))" +
"FILTER (?parent NOT IN (?exclude))" +
"}", TermDto.class).setParameter("type", typeUri)
.setParameter("term", term)
.setParameter("broader", URI.create(SKOS.BROADER))
.setParameter("graphs", graphs)
.setParameter("exclude", exclude != null ? exclude : Collections.emptyList())
.getResultList();
}

/**
* Returns true if the vocabulary does not contain any terms.
*
Expand Down Expand Up @@ -463,12 +490,13 @@ private void loadAdditionTermMetadata(AbstractTerm term, Set<URI> graphs) {
*/
private Set<TermInfo> loadSubTermInfo(AbstractTerm parent, Set<URI> graphs) {
final Stream<TermInfo> subTermsStream = em.createNativeQuery("SELECT ?entity ?label ?vocabulary WHERE {" +
"GRAPH ?g { ?entity ?broader ?parent ;" +
"a ?type ;" +
"GRAPH ?g { " +
"?entity a ?type ;" +
"?hasLabel ?label ." +
"FILTER (lang(?label) = ?labelLang) ." +
"}" +
"?entity ?inVocabulary ?vocabulary ." +
"?entity ?broader ?parent ; " + // Let broader be outside of the graph to allow including inferences
"?inVocabulary ?vocabulary ." +
"FILTER (?g in (?graphs))" +
"} ORDER BY LCASE(?label)", "TermInfo")
.setParameter("type", typeUri)
Expand Down Expand Up @@ -582,7 +610,10 @@ private List<TermDto> findAllImpl(String searchString, URI vocabularyIri) {
.setParameter("searchString", searchString, config.get(ConfigParam.LANGUAGE));
query.setDescriptor(descriptorFactory.termDescriptor(vocabularyIri));
final List<TermDto> terms = executeQueryAndLoadSubTerms(query, Collections.singleton(vocabularyCtx));
terms.forEach(t -> loadParentSubTerms(t, vocabularyCtx));
terms.forEach(t -> {
loadParentSubTerms(t, vocabularyCtx);
t.addParentTerms(loadInferredParentTerms(t, Collections.singleton(vocabularyCtx), t.getParentTerms()));
});
return terms;
} catch (RuntimeException e) {
throw new PersistenceException(e);
Expand Down Expand Up @@ -641,19 +672,17 @@ public List<Term> findAllSubTerms(Term parent) {
graphs.forEach(descriptor::addContext);
final TypedQuery<Term> query = em.createNativeQuery("SELECT DISTINCT ?term WHERE {" +
"GRAPH ?g { " +
"?term ?broader ?parent ;" +
"a ?type ;" +
"?hasLabel ?label . " +
"FILTER (lang(?label) = ?labelLang) ." +
"?term a ?type ." +
"}" +
"?term ?broader ?parent ." + // Let broader be outside of the graph to include inference
"FILTER (?g in (?graphs))" +
"} ORDER BY LCASE(?label)", Term.class).setParameter("type", typeUri)
"}", Term.class).setParameter("type", typeUri)
.setParameter("broader", URI.create(SKOS.BROADER))
.setParameter("parent", parent)
.setParameter("hasLabel", LABEL_PROP)
.setParameter("graphs", graphs)
.setParameter("labelLang", config.get(ConfigParam.LANGUAGE))
.setDescriptor(descriptor);
return executeQueryAndLoadSubTerms(query, graphs);
final List<Term> terms = executeQueryAndLoadSubTerms(query, graphs);
terms.sort(Comparator.comparing(t -> t.getLabel().get(config.get(ConfigParam.LANGUAGE))));
return terms;
}
}
12 changes: 12 additions & 0 deletions src/test/java/cz/cvut/kbss/termit/model/TermTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,16 @@ void toExcelHandlesNullAltLabelsAttribute() {
.forEach(v -> assertThat(row.getCell(3).getStringCellValue(),
containsString(v))));
}

@Test
void hasParentInSameVocabularyIncludesSuperTypesAsParents() {
final Term sut = Generator.generateTermWithId();
final URI vocabularyUri = Generator.generateUri();
sut.setGlossary(vocabularyUri);
final Term parent = Generator.generateTermWithId();
parent.setGlossary(vocabularyUri);
sut.setSuperTypes(Collections.singleton(parent));

assertTrue(sut.hasParentInSameVocabulary());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,18 @@ private Set<URI> generateCanonicalContainer() {
});
return statements.stream().map(s -> URI.create(s.getObject().stringValue())).collect(Collectors.toSet());
}

@Test
void termDescriptorCreatesDescriptorWithSuperTypesContextsContainingWorkspaceVocabulariesAndCanonicalCacheContainerContexts() throws Exception {
final FieldSpecification<Term, Term> superTypesFieldSpec = mock(FieldSpecification.class);
when(superTypesFieldSpec.getJavaField()).thenReturn(Term.class.getDeclaredField("superTypes"));
final Set<URI> workspaceVocabularies = IntStream.range(0, 3).mapToObj(i -> Generator.generateUri()).collect(Collectors.toSet());
final WorkspaceMetadata wsMetadata = workspaceMetadataProvider.getCurrentWorkspaceMetadata();
doReturn(workspaceVocabularies).when(wsMetadata).getVocabularyContexts();
final Set<URI> canonicalVocabularies = generateCanonicalContainer();
final Descriptor result = sut.termDescriptor(term);
final Set<URI> parentContexts = result.getAttributeDescriptor(superTypesFieldSpec).getContexts();
assertThat(parentContexts, hasItems(workspaceVocabularies.toArray(new URI[]{})));
assertThat(parentContexts, hasItems(canonicalVocabularies.toArray(new URI[]{})));
}
}
29 changes: 29 additions & 0 deletions src/test/java/cz/cvut/kbss/termit/persistence/dao/TermDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -727,4 +727,33 @@ void subTermLoadingSortsThemByLabel() {
assertEquals(child.getUri(), next.getUri());
}
}

@Test
void findAllBySearchStringAndVocabularyLoadsInferredParentTerms() {
final Term term = Generator.generateTermWithId(vocabulary.getUri());
final String searchString = "test";
term.getLabel().set(Constants.DEFAULT_LANGUAGE, searchString + " value");
final Term parent = Generator.generateTermWithId(vocabulary.getUri());
vocabulary.getGlossary().addRootTerm(parent);
transactional(() -> {
em.persist(term, descriptorFactory.termDescriptor(vocabulary));
em.persist(parent, descriptorFactory.termDescriptor(vocabulary));
em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary));
addTermInVocabularyRelationship(term, vocabulary.getUri());
addTermInVocabularyRelationship(parent, vocabulary.getUri());
insertInferredBroaderRelationship(term, parent, em);
});

final List<TermDto> result = sut.findAll(searchString, vocabulary);
assertEquals(1, result.size());
assertThat(result.get(0).getParentTerms(), hasItem(new TermDto(parent)));
}

static void insertInferredBroaderRelationship(Term child, Term parent, EntityManager em) {
final Repository repo = em.unwrap(Repository.class);
try (final RepositoryConnection conn = repo.getConnection()) {
final ValueFactory vf = conn.getValueFactory();
conn.add(vf.createIRI(child.getUri().toString()), vf.createIRI(SKOS.BROADER), vf.createIRI(parent.getUri().toString()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -847,4 +847,74 @@ void findAllRootsReturnsCorrectPageContent() {
final List<TermDto> result = sut.findAllRoots(PageRequest.of(1, pageSize));
assertEquals(expected, result);
}

@Test
void updateSupportsTermWithSupertypeInCanonicalContainer() {
final Term term = Generator.generateTermWithId();
final Term superType = Generator.generateTermWithId();
persistTermIntoCanonicalContainer(superType);
final EntityDescriptor termDescriptor = new EntityDescriptor(vocabulary.getUri());
transactional(() -> {
em.persist(term, termDescriptor);
Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em);
});

term.setSuperTypes(Collections.singleton(superType));
// This is normally inferred
term.setVocabulary(vocabulary.getUri());
transactional(() -> sut.update(term));
final Term result = em.find(Term.class, term.getUri());
assertEquals(term, result);
assertEquals(term, result);
assertThat(result.getSuperTypes(), hasItem(superType));
}

@Test
void findAllInCurrentWorkspaceBySearchStringLoadsInferredParentTerms() {
final String searchString = "search";
final Term term = Generator.generateTermWithId();
final Term parent = Generator.generateTermWithId();
term.getLabel().set(Constants.DEFAULT_LANGUAGE, searchString + " string label");
term.setGlossary(vocabulary.getGlossary().getUri());
final Vocabulary anotherVocabularyInWs = Generator.generateVocabularyWithId();
saveVocabulary(anotherVocabularyInWs);
parent.setGlossary(anotherVocabularyInWs.getGlossary().getUri());
transactional(() -> {
em.persist(term, new EntityDescriptor(vocabulary.getUri()));
vocabulary.getGlossary().addRootTerm(term);
em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary));
Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em);
em.persist(parent, new EntityDescriptor(anotherVocabularyInWs.getUri()));
Generator.addTermInVocabularyRelationship(parent, anotherVocabularyInWs.getUri(), em);
TermDaoTest.insertInferredBroaderRelationship(term, parent, em);
});

final List<TermDto> result = sut.findAll(searchString);
assertEquals(1, result.size());
assertThat(result.get(0).getParentTerms(), hasItem(new TermDto(parent)));
}

@Test
void findAllByPageableLoadsInferredParentTerms() {
final Term term = Generator.generateTermWithId();
final Term parent = Generator.generateTermWithId();
term.setGlossary(vocabulary.getGlossary().getUri());
final Vocabulary anotherVocabularyInWs = Generator.generateVocabularyWithId();
saveVocabulary(anotherVocabularyInWs);
parent.setGlossary(anotherVocabularyInWs.getGlossary().getUri());
transactional(() -> {
em.persist(term, new EntityDescriptor(vocabulary.getUri()));
vocabulary.getGlossary().addRootTerm(term);
em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary));
Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em);
em.persist(parent, new EntityDescriptor(anotherVocabularyInWs.getUri()));
Generator.addTermInVocabularyRelationship(parent, anotherVocabularyInWs.getUri(), em);
TermDaoTest.insertInferredBroaderRelationship(term, parent, em);
});

final List<TermDto> results = sut.findAll(Constants.DEFAULT_PAGE_SPEC);
final Optional<TermDto> res = results.stream().filter(t -> t.getUri().equals(term.getUri())).findFirst();
assertTrue(res.isPresent());
assertThat(res.get().getParentTerms(), hasItem(new TermDto(parent)));
}
}

0 comments on commit 885af15

Please sign in to comment.