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

Kbss cvut/termit UI#553 multilingual annotation #315

Merged
merged 7 commits into from
Nov 19, 2024
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