Skip to content

Commit

Permalink
Merge pull request #315 from kbss-cvut/kbss-cvut/termit-ui#553-multil…
Browse files Browse the repository at this point in the history
…ingual-annotation

Kbss cvut/termit UI#553 multilingual annotation
  • Loading branch information
ledsoft authored Nov 19, 2024
2 parents 742a017 + 6f10ef0 commit 54a1d35
Show file tree
Hide file tree
Showing 19 changed files with 501 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cz.cvut.kbss.termit.exception;

import cz.cvut.kbss.termit.model.Asset;
import cz.cvut.kbss.termit.model.resource.File;

/**
* Indicates that a language is not supported by the text analysis service.
*/
public class UnsupportedTextAnalysisLanguageException extends TermItException {

public UnsupportedTextAnalysisLanguageException(String message, Asset<?> asset) {
super(message, asset instanceof File ? "error.annotation.file.unsupportedLanguage" : "error.annotation.term.unsupportedLanguage");
}
}
31 changes: 15 additions & 16 deletions src/main/java/cz/cvut/kbss/termit/model/resource/File.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import cz.cvut.kbss.jopa.model.annotations.FetchType;
import cz.cvut.kbss.jopa.model.annotations.Inferred;
import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
import cz.cvut.kbss.jopa.model.annotations.Types;
import cz.cvut.kbss.jopa.vocabulary.DC;
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
import cz.cvut.kbss.termit.exception.TermItException;
import cz.cvut.kbss.termit.model.util.SupportsStorage;
import cz.cvut.kbss.termit.service.IdentifierResolver;
import cz.cvut.kbss.termit.util.Vocabulary;

import java.lang.reflect.Field;
import java.util.Objects;
import java.util.Set;

Expand All @@ -43,6 +43,9 @@ public class File extends Resource implements SupportsStorage {
@OWLObjectProperty(iri = Vocabulary.s_p_je_casti_dokumentu, fetch = FetchType.EAGER)
private Document document;

@OWLAnnotationProperty(iri = DC.Terms.LANGUAGE, simpleLiteral = true)
private String language;

@Types
private Set<String> types;

Expand All @@ -54,6 +57,14 @@ public void setDocument(Document document) {
this.document = document;
}

public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}

public Set<String> getTypes() {
return types;
}
Expand All @@ -73,15 +84,11 @@ public boolean equals(Object o) {
return Objects.equals(getUri(), file.getUri());
}

@Override
public int hashCode() {
return Objects.hash(getUri());
}

@Override
public String toString() {
return "File{" +
super.toString() + (document != null ? "document=<" + document.getUri() + ">" : "") + '}';
super.toString() + (language != null ? "@" + language : "") +
(document != null ? "document=<" + document.getUri() + ">" : "") + '}';
}

/**
Expand Down Expand Up @@ -109,12 +116,4 @@ public String getDirectoryName() {
return IdentifierResolver.normalizeToAscii(labelPart) + '_' + getUri().hashCode();
}
}

public static Field getDocumentField() {
try {
return File.class.getDeclaredField("document");
} catch (NoSuchFieldException e) {
throw new TermItException("Fatal error! Unable to retrieve \"document\" field.", e);
}
}
}
162 changes: 98 additions & 64 deletions src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,13 @@ public Vocabulary update(Vocabulary entity) {
/**
* Forcefully removes the specified vocabulary.
* <p>
* This deletes the whole graph of the vocabulary, all terms in the vocabulary's glossary and then removes the vocabulary itself. Extreme caution
* should be exercised when using this method. All relevant data, including documents and files, will be dropped.
* This deletes the whole graph of the vocabulary, all terms in the vocabulary's glossary and then removes the
* vocabulary itself. Extreme caution should be exercised when using this method. All relevant data, including
* documents and files, will be dropped.
* <p>
* Publishes {@link VocabularyWillBeRemovedEvent} before the actual removal to allow other services to clean up related resources (e.g., delete the document).
* Publishes {@link VocabularyWillBeRemovedEvent} before the actual removal to allow other services to clean up
* related resources (e.g., delete the document).
*
* @param entity The vocabulary to delete
*/
@ModifiesData
Expand All @@ -238,9 +241,9 @@ public void remove(Vocabulary entity) {
* <p>
* Forcefully removes the specified vocabulary.
* <p>
* This deletes all terms in the vocabulary's glossary and then removes the vocabulary itself.
* Extreme caution should be exercised when using this method,
* as it does not check for any references or usage and just drops all the relevant data.
* This deletes all terms in the vocabulary's glossary and then removes the vocabulary itself. Extreme caution
* should be exercised when using this method, as it does not check for any references or usage and just drops all
* the relevant data.
* <p>
* The document is not removed.
*/
Expand All @@ -250,27 +253,27 @@ public void removeVocabularyKeepDocument(Vocabulary entity) {

/**
* <p>
* Does not publish the {@link VocabularyWillBeRemovedEvent}.<br>
* You should use {@link #remove(Vocabulary)} instead.
* Does not publish the {@link VocabularyWillBeRemovedEvent}.<br> You should use {@link #remove(Vocabulary)}
* instead.
* <p>
* Forcefully removes the specified vocabulary.
* <p>
* This deletes all terms in the vocabulary's glossary and then removes the vocabulary itself. Extreme caution
* should be exercised when using this method, as it does not check for any references or usage and just drops all
* the relevant data.
* @param entity The vocabulary to delete
* @param dropGraph if false,
* executes {@code src/main/resources/query/remove/removeGlossaryTerms.ru} removing terms,
* their relations, model, glossary and vocabulary itself, keeps the document.
* When true, the whole vocabulary graph is dropped.
*
* @param entity The vocabulary to delete
* @param dropGraph if false, executes {@code src/main/resources/query/remove/removeGlossaryTerms.ru} removing
* terms, their relations, model, glossary and vocabulary itself, keeps the document. When true,
* the whole vocabulary graph is dropped.
*/
private void removeVocabulary(Vocabulary entity, boolean dropGraph) {
Objects.requireNonNull(entity);
LOG.debug("Forcefully removing vocabulary {} and all its contents.", entity);
try {
final URI vocabularyContext = contextMapper.getVocabularyContext(entity.getUri());

if(dropGraph) {
if (dropGraph) {
// drops whole named graph
em.createNativeQuery("DROP GRAPH ?context")
.setParameter("context", vocabularyContext)
Expand Down Expand Up @@ -319,8 +322,8 @@ public Optional<Glossary> findGlossary(URI uri) {
}

/**
* Checks whether terms from the {@code subjectVocabulary} reference (as parent terms) any terms from the {@code
* targetVocabulary}.
* Checks whether terms from the {@code subjectVocabulary} reference (as parent terms) any terms from the
* {@code targetVocabulary}.
*
* @param subjectVocabulary Subject vocabulary identifier
* @param targetVocabulary Target vocabulary identifier
Expand Down Expand Up @@ -399,33 +402,35 @@ public List<AggregatedChangeInfo> getChangesOfContent(Vocabulary vocabulary) {
* Gets content change records of the specified vocabulary.
*
* @param vocabulary Vocabulary whose content changes to get
* @param pageReq Specification of the size and number of the page to return
* @param pageReq Specification of the size and number of the page to return
* @return List of change records, ordered by date in descending order
*/
public List<AbstractChangeRecord> getDetailedHistoryOfContent(Vocabulary vocabulary, Pageable pageReq) {
Objects.requireNonNull(vocabulary);
return createDetailedContentChangesQuery(vocabulary, pageReq).getResultList();
}

private TypedQuery<AbstractChangeRecord> createDetailedContentChangesQuery(Vocabulary vocabulary, Pageable pageReq) {
private TypedQuery<AbstractChangeRecord> createDetailedContentChangesQuery(Vocabulary vocabulary,
Pageable pageReq) {
return em.createNativeQuery("""
SELECT ?record WHERE {
?term ?inVocabulary ?vocabulary ;
a ?termType .
?record a ?changeRecord ;
?relatesTo ?term ;
?hasTime ?timestamp .
OPTIONAL { ?record ?hasChangedAttribute ?attribute . }
} ORDER BY DESC(?timestamp) ?attribute
""", AbstractChangeRecord.class)
SELECT ?record WHERE {
?term ?inVocabulary ?vocabulary ;
a ?termType .
?record a ?changeRecord ;
?relatesTo ?term ;
?hasTime ?timestamp .
OPTIONAL { ?record ?hasChangedAttribute ?attribute . }
} ORDER BY DESC(?timestamp) ?attribute
""", AbstractChangeRecord.class)
.setParameter("inVocabulary",
URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku))
URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku))
.setParameter("vocabulary", vocabulary)
.setParameter("termType", URI.create(SKOS.CONCEPT))
.setParameter("termType", URI.create(SKOS.CONCEPT))
.setParameter("changeRecord", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_c_zmena))
.setParameter("relatesTo", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_zmenenou_entitu))
.setParameter("hasTime", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_datum_a_cas_modifikace))
.setParameter("hasChangedAttribute", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_zmeneny_atribut))
.setParameter("hasChangedAttribute",
URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_zmeneny_atribut))
.setFirstResult((int) pageReq.getOffset())
.setMaxResults(pageReq.getPageSize());
}
Expand Down Expand Up @@ -580,16 +585,17 @@ public List<RdfsStatement> getVocabularyRelations(Vocabulary vocabulary, Collect

try {
return em.createNativeQuery("""
SELECT DISTINCT ?object ?relation ?subject {
?object a ?vocabularyType ;
?relation ?subject .
FILTER(?object != ?subject) .
FILTER(?relation NOT IN (?excluded)) .
} ORDER BY ?object ?relation
""", "RDFStatement")
SELECT DISTINCT ?object ?relation ?subject {
?object a ?vocabularyType ;
?relation ?subject .
FILTER(?object != ?subject) .
FILTER(?relation NOT IN (?excluded)) .
} ORDER BY ?object ?relation
""", "RDFStatement")
.setParameter("subject", vocabularyUri)
.setParameter("excluded", excludedRelations)
.setParameter("vocabularyType", URI.create(EntityToOwlClassMapper.getOwlClassForEntity(Vocabulary.class)))
.setParameter("excluded", excludedRelations)
.setParameter("vocabularyType",
URI.create(EntityToOwlClassMapper.getOwlClassForEntity(Vocabulary.class)))
.getResultList();
} catch (RuntimeException e) {
throw new PersistenceException(e);
Expand All @@ -607,31 +613,31 @@ public List<RdfsStatement> getTermRelations(Vocabulary vocabulary) {

try {
return em.createNativeQuery("""
SELECT DISTINCT ?object ?relation ?subject WHERE {
?term a ?termType;
?inVocabulary ?vocabulary .
{
?term ?relation ?secondTerm .
?secondTerm a ?termType;
?inVocabulary ?secondVocabulary .
BIND(?term as ?object)
BIND(?secondTerm as ?subject)
} UNION {
?secondTerm ?relation ?term .
?secondTerm a ?termType;
?inVocabulary ?secondVocabulary .
BIND(?secondTerm as ?object)
BIND(?term as ?subject)
}
FILTER(?relation IN (?deniedRelations))
FILTER(?object != ?subject)
FILTER(?secondVocabulary != ?vocabulary)
} ORDER by ?object ?relation ?subject
""", "RDFStatement"
SELECT DISTINCT ?object ?relation ?subject WHERE {
?term a ?termType;
?inVocabulary ?vocabulary .
{
?term ?relation ?secondTerm .
?secondTerm a ?termType;
?inVocabulary ?secondVocabulary .
BIND(?term as ?object)
BIND(?secondTerm as ?subject)
} UNION {
?secondTerm ?relation ?term .
?secondTerm a ?termType;
?inVocabulary ?secondVocabulary .
BIND(?secondTerm as ?object)
BIND(?term as ?subject)
}
FILTER(?relation IN (?deniedRelations))
FILTER(?object != ?subject)
FILTER(?secondVocabulary != ?vocabulary)
} ORDER by ?object ?relation ?subject
""", "RDFStatement"
).setMaxResults(DEFAULT_PAGE_SIZE)
.setParameter("termType", termType)
.setParameter("inVocabulary", inVocabulary)
Expand All @@ -642,4 +648,32 @@ public List<RdfsStatement> getTermRelations(Vocabulary vocabulary) {
throw new PersistenceException(e);
}
}

/**
* Returns the list of all distinct languages (language tags) used by terms in the specified vocabulary.
*
* @param vocabularyUri Vocabulary identifier
* @return List of distinct languages
*/
public List<String> getLanguages(URI vocabularyUri) {
Objects.requireNonNull(vocabularyUri);
try {
return em.createNativeQuery("""
SELECT DISTINCT ?lang WHERE {
?x a ?type ;
?inVocabulary ?vocabulary ;
?labelProp ?label .
BIND (LANG(?label) as ?lang)
}
""", String.class)
.setParameter("type", URI.create(SKOS.CONCEPT))
.setParameter("inVocabulary",
URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku))
.setParameter("vocabulary", vocabularyUri)
.setParameter("labelProp", URI.create(SKOS.PREF_LABEL))
.getResultList();
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,22 @@ public List<AbstractChangeRecord> getDetailedHistoryOfContent(
return vocabularyService.getDetailedHistoryOfContent(vocabulary, pageReq);
}

@Operation(security = {@SecurityRequirement(name = "bearer-key")},
description = "Gets a list of languages used in the vocabulary.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "List of languages.")
})
@GetMapping(value = "/{localName}/languages", produces = {MediaType.APPLICATION_JSON_VALUE, JsonLd.MEDIA_TYPE})
public List<String> getLanguages(
@Parameter(description = ApiDoc.ID_LOCAL_NAME_DESCRIPTION,
example = ApiDoc.ID_LOCAL_NAME_EXAMPLE) @PathVariable String localName,
@Parameter(description = ApiDoc.ID_NAMESPACE_DESCRIPTION,
example = ApiDoc.ID_NAMESPACE_EXAMPLE) @RequestParam(name = QueryParams.NAMESPACE,
required = false) Optional<String> namespace) {
final URI vocabularyUri = resolveVocabularyUri(localName, namespace);
return vocabularyService.getLanguages(vocabularyUri);
}

@Operation(security = {@SecurityRequirement(name = "bearer-key")},
description = "Updates metadata of vocabulary with the specified identifier.")
@ApiResponses({
Expand Down
Loading

0 comments on commit 54a1d35

Please sign in to comment.