From 1486e9f7f4d628f21f058307b73f31ef3b44ae40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Ka=C5=88ka?= Date: Fri, 1 Nov 2024 09:26:34 +0100 Subject: [PATCH] [Enhancement kbss-cvut/termit-ui#520] Update vocabulary content history detailed endpoint allowing result filtering. --- .../VocabularyContentChangeFilterDto.java | 45 ++++++++++ .../termit/persistence/dao/VocabularyDao.java | 90 ++++++++++++++----- .../termit/rest/VocabularyController.java | 17 +++- .../service/business/VocabularyService.java | 5 +- .../VocabularyRepositoryService.java | 5 +- 5 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 src/main/java/cz/cvut/kbss/termit/dto/filter/VocabularyContentChangeFilterDto.java diff --git a/src/main/java/cz/cvut/kbss/termit/dto/filter/VocabularyContentChangeFilterDto.java b/src/main/java/cz/cvut/kbss/termit/dto/filter/VocabularyContentChangeFilterDto.java new file mode 100644 index 000000000..c09fa7988 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/termit/dto/filter/VocabularyContentChangeFilterDto.java @@ -0,0 +1,45 @@ +package cz.cvut.kbss.termit.dto.filter; + +import java.net.URI; + +/** + * Represents parameters for filtering vocabulary content changes. + */ +public class VocabularyContentChangeFilterDto { + private String termName; + private String changedAttributeName; + private String authorName; + private URI changeType; + + public String getTermName() { + return termName; + } + + public void setTermName(String termName) { + this.termName = termName; + } + + public String getChangedAttributeName() { + return changedAttributeName; + } + + public void setChangedAttributeName(String changedAttributeName) { + this.changedAttributeName = changedAttributeName; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public URI getChangeType() { + return changeType; + } + + public void setChangeType(URI changeType) { + this.changeType = changeType; + } +} diff --git a/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java b/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java index 02230ea73..dab8b0619 100644 --- a/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java +++ b/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java @@ -21,6 +21,7 @@ import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.model.query.TypedQuery; 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.termit.asset.provenance.ModifiesData; import cz.cvut.kbss.termit.asset.provenance.SupportsLastModification; @@ -28,6 +29,7 @@ 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.dto.filter.VocabularyContentChangeFilterDto; import cz.cvut.kbss.termit.event.AssetPersistEvent; import cz.cvut.kbss.termit.event.AssetUpdateEvent; import cz.cvut.kbss.termit.event.BeforeAssetDeleteEvent; @@ -43,6 +45,7 @@ import cz.cvut.kbss.termit.model.validation.ValidationResult; import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; import cz.cvut.kbss.termit.persistence.context.VocabularyContextMapper; +import cz.cvut.kbss.termit.persistence.dao.changetracking.ChangeTrackingContextResolver; import cz.cvut.kbss.termit.persistence.snapshot.AssetSnapshotLoader; import cz.cvut.kbss.termit.persistence.validation.VocabularyContentValidator; import cz.cvut.kbss.termit.service.snapshot.SnapshotProvider; @@ -88,6 +91,7 @@ public class VocabularyDao extends BaseAssetDao "} GROUP BY ?date HAVING (?cnt > 0) ORDER BY ?date"; private static final String REMOVE_GLOSSARY_TERMS_QUERY_FILE = "remove/removeGlossaryTerms.ru"; + private final ChangeTrackingContextResolver changeTrackingContextResolver; private volatile long lastModified; @@ -97,11 +101,13 @@ public class VocabularyDao extends BaseAssetDao @Autowired public VocabularyDao(EntityManager em, Configuration config, DescriptorFactory descriptorFactory, - VocabularyContextMapper contextMapper, ApplicationContext context) { + VocabularyContextMapper contextMapper, ApplicationContext context, + ChangeTrackingContextResolver changeTrackingContextResolver) { super(Vocabulary.class, em, config.getPersistence(), descriptorFactory); this.contextMapper = contextMapper; refreshLastModified(); this.context = context; + this.changeTrackingContextResolver = changeTrackingContextResolver; } @Override @@ -402,31 +408,75 @@ public List getChangesOfContent(Vocabulary vocabulary) { * @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 getDetailedHistoryOfContent(Vocabulary vocabulary, Pageable pageReq) { + public List getDetailedHistoryOfContent(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) { Objects.requireNonNull(vocabulary); - return createDetailedContentChangesQuery(vocabulary, pageReq).getResultList(); + return createDetailedContentChangesQuery(vocabulary, filter, pageReq).getResultList(); } - private TypedQuery createDetailedContentChangesQuery(Vocabulary vocabulary, Pageable pageReq) { - return em.createNativeQuery(""" + private TypedQuery createDetailedContentChangesQuery(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) { + TypedQuery query = em.createNativeQuery(""" SELECT ?record WHERE { - ?term ?inVocabulary ?vocabulary ; - a ?termType . - ?record a ?changeRecord ; - ?relatesTo ?term ; - ?hasTime ?timestamp . - OPTIONAL { ?record ?hasChangedAttribute ?attribute . } + GRAPH ?changeContext { + ?record a ?changeRecord . + } + ?changeRecord ?subClassOf+ ?zmena . + ?record ?relatesTo ?term ; + ?hasTime ?timestamp ; + ?hasAuthor ?author . + ?author ?hasFirstName ?firstName ; + ?hasLastName ?lastName . + BIND(CONCAT(?firstName, " ", ?lastName) as ?authorFullName) + OPTIONAL { + ?record ?hasChangedAttribute ?attribute . + OPTIONAL { + ?attribute ?hasRdfsLabel ?changedAttributeName . + } + } + OPTIONAL { + ?term ?inVocabulary ?vocabulary ; + a ?termType ; + ?hasLabel ?label . + } + OPTIONAL { + ?record ?hasRdfsLabel ?label . + } + BIND(?termName as ?termNameVal) + BIND(?authorName as ?authorNameVal) + BIND(?changedAttributeName as ?changedAttributeNameVal) + FILTER (!BOUND(?termNameVal) || CONTAINS(LCASE(?label), LCASE(?termName))) + FILTER (!BOUND(?authorNameVal) || CONTAINS(LCASE(?authorFullName), LCASE(?authorName))) + FILTER (!BOUND(?changedAttributeName) || !BOUND(?changedAttributeNameVal) || CONTAINS(LCASE(?changedAttributeName), LCASE(?attributeName))) } ORDER BY DESC(?timestamp) ?attribute """, AbstractChangeRecord.class) - .setParameter("inVocabulary", - URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku)) - .setParameter("vocabulary", vocabulary) - .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)) - .setFirstResult((int) pageReq.getOffset()) + .setParameter("changeContext", changeTrackingContextResolver.resolveChangeTrackingContext(vocabulary)) + .setParameter("subClassOf", URI.create(RDFS.SUB_CLASS_OF)) + .setParameter("zmena", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_c_zmena)) + .setParameter("inVocabulary", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_je_pojmem_ze_slovniku)) + .setParameter("vocabulary", vocabulary) + .setParameter("termType", URI.create(SKOS.CONCEPT)) + .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("hasLabel", URI.create(SKOS.PREF_LABEL)) // term label + .setParameter("hasAuthor", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_editora)) // record has author + .setParameter("hasFirstName", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_krestni_jmeno)) + .setParameter("hasLastName", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_prijmeni)) + .setParameter("hasRdfsLabel", URI.create(RDFS.LABEL)); // changed attribute label + + if(!Utils.isBlank(filter.getTermName())) { + query = query.setParameter("termName", filter.getTermName().trim()); + } + if (!Utils.isBlank(filter.getAuthorName())) { + query = query.setParameter("authorName", filter.getAuthorName().trim()); + } + if (filter.getChangeType() != null) { + query = query.setParameter("changeRecord", filter.getChangeType()); + } + if (!Utils.isBlank(filter.getChangedAttributeName())) { + query = query.setParameter("attributeName", filter.getChangedAttributeName().trim()); + } + + return query.setFirstResult((int) pageReq.getOffset()) .setMaxResults(pageReq.getPageSize()); } diff --git a/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java b/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java index e8cd5afb4..72aa526a6 100644 --- a/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java +++ b/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java @@ -22,6 +22,7 @@ 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.filter.VocabularyContentChangeFilterDto; import cz.cvut.kbss.termit.dto.listing.VocabularyDto; import cz.cvut.kbss.termit.model.Vocabulary; import cz.cvut.kbss.termit.model.acl.AccessControlRecord; @@ -301,6 +302,15 @@ public List getDetailedHistoryOfContent( @Parameter(description = ApiDoc.ID_NAMESPACE_DESCRIPTION, example = ApiDoc.ID_NAMESPACE_EXAMPLE) @RequestParam(name = QueryParams.NAMESPACE, required = false) Optional namespace, + @Parameter(description = "Term name to be used in filtering.") + @RequestParam(name = "term", required = false, defaultValue = "") String termName, + @Parameter(description = "Change type to be used in filtering.") + @RequestParam(name = "type", required = false) URI changeType, + @Parameter(description = "Author name to be used in filtering.") + @RequestParam(name = "author", required = false, defaultValue = "") String authorName, + @Parameter(description = "Changed attribute name to be used in filtering.") + @RequestParam(name = "attribute", required = false, defaultValue = "") String changedAttributeName, + @Parameter(description = ApiDocConstants.PAGE_SIZE_DESCRIPTION) @RequestParam( name = Constants.QueryParams.PAGE_SIZE, required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize, @@ -308,7 +318,12 @@ public List getDetailedHistoryOfContent( name = Constants.QueryParams.PAGE, required = false, defaultValue = DEFAULT_PAGE) Integer pageNo) { final Pageable pageReq = createPageRequest(pageSize, pageNo); final Vocabulary vocabulary = vocabularyService.getReference(resolveVocabularyUri(localName, namespace)); - return vocabularyService.getDetailedHistoryOfContent(vocabulary, pageReq); + final VocabularyContentChangeFilterDto filter = new VocabularyContentChangeFilterDto(); + filter.setTermName(termName); + filter.setChangeType(changeType); + filter.setAuthorName(authorName); + filter.setChangedAttributeName(changedAttributeName); + return vocabularyService.getDetailedHistoryOfContent(vocabulary, filter, pageReq); } @Operation(security = {@SecurityRequirement(name = "bearer-key")}, diff --git a/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java b/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java index fe6d9b20a..a0f252171 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java @@ -22,6 +22,7 @@ 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.filter.VocabularyContentChangeFilterDto; import cz.cvut.kbss.termit.dto.listing.TermDto; import cz.cvut.kbss.termit.dto.listing.VocabularyDto; import cz.cvut.kbss.termit.event.VocabularyContentModifiedEvent; @@ -319,8 +320,8 @@ public List getChangesOfContent(Vocabulary vocabulary) { * @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 getDetailedHistoryOfContent(Vocabulary vocabulary, Pageable pageReq) { - return repositoryService.getDetailedHistoryOfContent(vocabulary, pageReq); + public List getDetailedHistoryOfContent(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) { + return repositoryService.getDetailedHistoryOfContent(vocabulary, filter, pageReq); } /** diff --git a/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java b/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java index 6be0b86d4..43e7cde00 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java @@ -21,6 +21,7 @@ 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.dto.filter.VocabularyContentChangeFilterDto; import cz.cvut.kbss.termit.dto.listing.VocabularyDto; import cz.cvut.kbss.termit.dto.mapper.DtoMapper; import cz.cvut.kbss.termit.exception.AssetRemovalException; @@ -228,8 +229,8 @@ public List getChangesOfContent(Vocabulary vocabulary) { * @return List of change records, ordered by date in descending order */ @Transactional(readOnly = true) - public List getDetailedHistoryOfContent(Vocabulary vocabulary, Pageable pageReq) { - return vocabularyDao.getDetailedHistoryOfContent(vocabulary, pageReq); + public List getDetailedHistoryOfContent(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) { + return vocabularyDao.getDetailedHistoryOfContent(vocabulary, filter, pageReq); } @CacheEvict(allEntries = true)