Skip to content

Commit

Permalink
Merge branch 'development' into kbss-cvut/termit-ui#449-excel-import
Browse files Browse the repository at this point in the history
  • Loading branch information
ledsoft authored Aug 12, 2024
2 parents cdeeac5 + 47817b5 commit 94468a1
Show file tree
Hide file tree
Showing 15 changed files with 698 additions and 46 deletions.
74 changes: 74 additions & 0 deletions src/main/java/cz/cvut/kbss/termit/dto/RdfsStatement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cz.cvut.kbss.termit.dto;

import cz.cvut.kbss.jopa.model.annotations.ConstructorResult;
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.ParticipationConstraints;
import cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping;
import cz.cvut.kbss.jopa.model.annotations.VariableResult;
import cz.cvut.kbss.jopa.model.annotations.util.NonEntity;
import cz.cvut.kbss.jopa.vocabulary.RDF;

import java.io.Serializable;
import java.net.URI;

/**
* Utility class describing a generic {@link #relation} between an {@link #object} and {@link #subject}
*/
@NonEntity
@OWLClass(iri = RDF.STATEMENT)
@SparqlResultSetMapping(name = "RDFStatement",
classes = {@ConstructorResult(targetClass = RdfsStatement.class,
variables = {
@VariableResult(name = "object", type = URI.class),
@VariableResult(name = "relation", type = URI.class),
@VariableResult(name = "subject", type = URI.class),
})})
public class RdfsStatement implements Serializable {

@ParticipationConstraints(nonEmpty = true)
@OWLObjectProperty(iri = RDF.OBJECT)
private URI object;

@ParticipationConstraints(nonEmpty = true)
@OWLAnnotationProperty(iri = RDF.PREDICATE)
private URI relation;

@ParticipationConstraints(nonEmpty = true)
@OWLObjectProperty(iri = RDF.SUBJECT)
private URI subject;

public RdfsStatement() {
}

public RdfsStatement(URI object, URI relation, URI subject) {
this.object = object;
this.relation = relation;
this.subject = subject;
}

public URI getObject() {
return object;
}

public void setObject(URI object) {
this.object = object;
}

public URI getRelation() {
return relation;
}

public void setRelation(URI relation) {
this.relation = relation;
}

public URI getSubject() {
return subject;
}

public void setSubject(URI subject) {
this.subject = subject;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cz.cvut.kbss.termit.event;

import org.springframework.context.ApplicationEvent;

import java.net.URI;

/**
* Indicates that a Vocabulary will be removed
*/
public class VocabularyWillBeRemovedEvent extends ApplicationEvent {
private final URI vocabulary;

public VocabularyWillBeRemovedEvent(Object source, URI vocabulary) {
super(source);
this.vocabulary = vocabulary;
}

public URI getVocabulary() {
return vocabulary;
}
}
162 changes: 141 additions & 21 deletions src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@
import cz.cvut.kbss.termit.asset.provenance.SupportsLastModification;
import cz.cvut.kbss.termit.dto.AggregatedChangeInfo;
import cz.cvut.kbss.termit.dto.PrefixDeclaration;
import cz.cvut.kbss.termit.dto.RdfsStatement;
import cz.cvut.kbss.termit.dto.Snapshot;
import cz.cvut.kbss.termit.event.AssetPersistEvent;
import cz.cvut.kbss.termit.event.AssetUpdateEvent;
import cz.cvut.kbss.termit.event.RefreshLastModifiedEvent;
import cz.cvut.kbss.termit.event.VocabularyWillBeRemovedEvent;
import cz.cvut.kbss.termit.exception.PersistenceException;
import cz.cvut.kbss.termit.model.Glossary;
import cz.cvut.kbss.termit.model.Term;
import cz.cvut.kbss.termit.model.Vocabulary;
import cz.cvut.kbss.termit.model.resource.Document;
import cz.cvut.kbss.termit.model.util.EntityToOwlClassMapper;
import cz.cvut.kbss.termit.model.validation.ValidationResult;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.VocabularyContextMapper;
Expand All @@ -51,7 +55,17 @@

import java.net.URI;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import static cz.cvut.kbss.termit.util.Constants.DEFAULT_PAGE_SIZE;
import static cz.cvut.kbss.termit.util.Constants.SKOS_CONCEPT_MATCH_RELATIONSHIPS;

@Repository
public class VocabularyDao extends BaseAssetDao<Vocabulary>
Expand Down Expand Up @@ -197,41 +211,75 @@ 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.
* <p>
* 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
@Override
public void remove(Vocabulary entity) {
Objects.requireNonNull(entity);
try {
find(entity.getUri()).ifPresent(elem -> {
em.remove(elem);
refreshLastModified();
});
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
eventPublisher.publishEvent(new VocabularyWillBeRemovedEvent(this, entity.getUri()));
this.removeVocabulary(entity, true);
}

/**
* Does not publish the {@link VocabularyWillBeRemovedEvent}.
* <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.
* <p>
* The document is not removed.
*/
public void removeVocabularyKeepDocument(Vocabulary entity) {
this.removeVocabulary(entity, false);
}

/**
* <p>
* 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.
*/
@ModifiesData
public void forceRemove(Vocabulary entity) {
private void removeVocabulary(Vocabulary entity, boolean dropGraph) {
Objects.requireNonNull(entity);
LOG.debug("Forcefully removing vocabulary {} and all its contents.", entity);
try {
final URI context = contextMapper.getVocabularyContext(entity);
em.createNativeQuery(Utils.loadQuery(REMOVE_GLOSSARY_TERMS_QUERY_FILE))
.setParameter("g", context)
.setParameter("vocabulary", entity)
.executeUpdate();
remove(entity);
em.getEntityManagerFactory().getCache().evict(context);
final URI vocabularyContext = contextMapper.getVocabularyContext(entity.getUri());

if(dropGraph) {
// drops whole named graph
em.createNativeQuery("DROP GRAPH ?context")
.setParameter("context", vocabularyContext)
.executeUpdate();
} else {
// removes all terms and their relations from named graph
em.createNativeQuery(Utils.loadQuery(REMOVE_GLOSSARY_TERMS_QUERY_FILE))
.setParameter("g", vocabularyContext)
.setParameter("vocabulary", entity.getUri())
.executeUpdate();
}

find(entity.getUri()).ifPresent(em::remove);
refreshLastModified();
em.getEntityManagerFactory().getCache().evict(vocabularyContext);
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
Expand Down Expand Up @@ -376,7 +424,7 @@ public boolean isEmpty(Vocabulary vocabulary) {
"?inVocabulary ?vocabulary ." +
" }", Boolean.class)
.setParameter("type", URI.create(SKOS.CONCEPT))
.setParameter("vocabulary", vocabulary)
.setParameter("vocabulary", vocabulary.getUri())
.setParameter("inVocabulary",
URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku))
.getSingleResult();
Expand Down Expand Up @@ -479,4 +527,76 @@ public PrefixDeclaration resolvePrefix(URI vocabularyUri) {
throw new PersistenceException(e);
}
}

/**
* @return all relations between specified vocabulary and all other vocabularies
*/
public List<RdfsStatement> getVocabularyRelations(Vocabulary vocabulary, Collection<URI> excludedRelations) {
Objects.requireNonNull(vocabulary);
final URI vocabularyUri = vocabulary.getUri();

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")
.setParameter("subject", vocabularyUri)
.setParameter("excluded", excludedRelations)
.setParameter("vocabularyType", URI.create(EntityToOwlClassMapper.getOwlClassForEntity(Vocabulary.class)))
.getResultList();
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
}

/**
* @return all relations between terms in specified vocabulary and all terms from any other vocabulary
*/
public List<RdfsStatement> getTermRelations(Vocabulary vocabulary) {
Objects.requireNonNull(vocabulary);
final URI vocabularyUri = vocabulary.getUri();
final URI termType = URI.create(EntityToOwlClassMapper.getOwlClassForEntity(Term.class));
final URI inVocabulary = URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku);

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"
).setMaxResults(DEFAULT_PAGE_SIZE)
.setParameter("termType", termType)
.setParameter("inVocabulary", inVocabulary)
.setParameter("vocabulary", vocabularyUri)
.setParameter("deniedRelations", SKOS_CONCEPT_MATCH_RELATIONSHIPS)
.getResultList();
} catch (RuntimeException e) {
throw new PersistenceException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private void clearVocabulary(Vocabulary newVocabulary) {
possibleVocabulary.ifPresent(toRemove -> {
newVocabulary.setDocument(toRemove.getDocument());
newVocabulary.setAcl(toRemove.getAcl());
vocabularyDao.forceRemove(toRemove);
vocabularyDao.removeVocabularyKeepDocument(toRemove);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public synchronized void scheduleForRemoval(@NonNull URI contextUri) {
*/
@Transactional
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.MINUTES)
public void runContextRemoval() {
public synchronized void runContextRemoval() {
LOG.trace("Running scheduled repository context removal.");
contextsToRemove.forEach(g -> {
LOG.trace("Dropping repository context {}.", Utils.uriToString(g));
Expand Down
48 changes: 44 additions & 4 deletions src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.termit.dto.AggregatedChangeInfo;
import cz.cvut.kbss.termit.dto.RdfsStatement;
import cz.cvut.kbss.termit.dto.Snapshot;
import cz.cvut.kbss.termit.dto.acl.AccessControlListDto;
import cz.cvut.kbss.termit.dto.listing.VocabularyDto;
Expand Down Expand Up @@ -367,10 +368,49 @@ public void removeVocabulary(@Parameter(description = ApiDoc.ID_LOCAL_NAME_DESCR
@RequestParam(name = QueryParams.NAMESPACE,
required = false) Optional<String> namespace) {
final URI identifier = resolveIdentifier(namespace.orElse(config.getNamespace().getVocabulary()), localName);
vocabularyService.find(identifier).ifPresent(toRemove -> {
vocabularyService.remove(toRemove);
LOG.debug("Vocabulary {} removed.", toRemove);
});
final Vocabulary vocabulary = vocabularyService.findRequired(identifier);
vocabularyService.remove(vocabulary);
LOG.debug("Vocabulary {} removed.", vocabulary);
}

@Operation(security = {@SecurityRequirement(name = "bearer-key")},
description = "Returns relations with other vocabularies")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "A collection of vocabulary relations"),
@ApiResponse(responseCode = "404", description = ApiDoc.ID_NOT_FOUND_DESCRIPTION),
})
@GetMapping(value = "/{localName}/relations")
public List<RdfsStatement> relations(@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 identifier = resolveIdentifier(namespace.orElse(config.getNamespace().getVocabulary()), localName);
final Vocabulary vocabulary = vocabularyService.findRequired(identifier);

return vocabularyService.getVocabularyRelations(vocabulary);
}

@Operation(security = {@SecurityRequirement(name = "bearer-key")},
description = "Returns relations with terms from other vocabularies")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "A collection of term relations"),
@ApiResponse(responseCode = "404", description = ApiDoc.ID_NOT_FOUND_DESCRIPTION),
})
@GetMapping(value = "/{localName}/terms/relations")
public List<RdfsStatement> termsRelations(@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 identifier = resolveIdentifier(namespace.orElse(config.getNamespace().getVocabulary()), localName);
final Vocabulary vocabulary = vocabularyService.findRequired(identifier);

return vocabularyService.getTermRelations(vocabulary);
}

@Operation(description = "Validates the terms in a vocabulary with the specified identifier.")
Expand Down
Loading

0 comments on commit 94468a1

Please sign in to comment.