Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enhancement/85 subclassof broader #86

Merged
merged 8 commits into from
May 12, 2021
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
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)
ledsoft marked this conversation as resolved.
Show resolved Hide resolved
// 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
66 changes: 52 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,15 @@ 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;
initParentTerms(dto);
dto.getParentTerms().addAll(loadInferredParentTerms(dto, contexts, dto.getParentTerms()));
ledsoft marked this conversation as resolved.
Show resolved Hide resolved
});
}
return result;
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
Expand Down Expand Up @@ -409,12 +417,39 @@ 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 -> {
initParentTerms(t);
t.getParentTerms().addAll(loadInferredParentTerms(t, contexts, t.getParentTerms()));
});
return result;
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
}

private void initParentTerms(TermDto t) {
if (t.getParentTerms() == null) {
t.setParentTerms(new LinkedHashSet<>());
}
}

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)
.getResultList();
}

/**
* Returns true if the vocabulary does not contain any terms.
*
Expand Down Expand Up @@ -463,12 +498,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 +618,11 @@ 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);
initParentTerms(t);
t.getParentTerms().addAll(loadInferredParentTerms(t, Collections.singleton(vocabularyCtx), t.getParentTerms()));
});
return terms;
} catch (RuntimeException e) {
throw new PersistenceException(e);
Expand Down Expand Up @@ -641,19 +681,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[]{})));
}
}
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)));
}
}