Skip to content

Commit

Permalink
[Enhancement kbss-cvut/termit-ui#520] Move change record implementati…
Browse files Browse the repository at this point in the history
…on to ChangeRecordDao
  • Loading branch information
lukaskabc committed Nov 10, 2024
1 parent 8bb5fb3 commit c3b4cfd
Show file tree
Hide file tree
Showing 19 changed files with 262 additions and 165 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package cz.cvut.kbss.termit.dto.filter;

import com.fasterxml.jackson.annotation.JsonIgnore;
import cz.cvut.kbss.termit.util.Utils;

import java.net.URI;
import java.util.Objects;

/**
* Represents parameters for filtering vocabulary content changes.
*/
public class VocabularyContentChangeFilterDto {
private String termName = "";
public class ChangeRecordFilterDto {
private String assetLabel = "";
private String changedAttributeName = "";
private String authorName = "";
private URI changeType = null;

public String getTermName() {
return termName;
public String getAssetLabel() {
return assetLabel;
}

public void setTermName(String termName) {
this.termName = termName;
public void setAssetLabel(String assetLabel) {
this.assetLabel = assetLabel;
}

public String getChangedAttributeName() {
Expand All @@ -44,18 +47,29 @@ public void setChangeType(URI changeType) {
this.changeType = changeType;
}

/**
* @return true when all attributes are empty or null
*/
@JsonIgnore
public boolean isEmpty() {
return Utils.isBlank(assetLabel) &&
Utils.isBlank(changedAttributeName) &&
Utils.isBlank(authorName) &&
changeType == null;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof VocabularyContentChangeFilterDto that)) return false;
return Objects.equals(termName, that.termName) &&
if (!(o instanceof ChangeRecordFilterDto that)) return false;
return Objects.equals(assetLabel, that.assetLabel) &&
Objects.equals(changedAttributeName, that.changedAttributeName) &&
Objects.equals(authorName, that.authorName) &&
Objects.equals(changeType, that.changeType);
}

@Override
public int hashCode() {
return Objects.hash(termName, changedAttributeName, authorName, changeType);
return Objects.hash(assetLabel, changedAttributeName, authorName, changeType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@

import cz.cvut.kbss.jopa.model.EntityManager;
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;
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.dto.filter.VocabularyContentChangeFilterDto;
import cz.cvut.kbss.termit.dto.filter.ChangeRecordFilterDto;
import cz.cvut.kbss.termit.event.AssetPersistEvent;
import cz.cvut.kbss.termit.event.AssetUpdateEvent;
import cz.cvut.kbss.termit.event.BeforeAssetDeleteEvent;
Expand All @@ -45,6 +43,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.ChangeRecordDao;
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;
Expand Down Expand Up @@ -92,6 +91,7 @@ public class VocabularyDao extends BaseAssetDao<Vocabulary>

private static final String REMOVE_GLOSSARY_TERMS_QUERY_FILE = "remove/removeGlossaryTerms.ru";
private final ChangeTrackingContextResolver changeTrackingContextResolver;
private final ChangeRecordDao changeRecordDao;

private volatile long lastModified;

Expand All @@ -102,12 +102,13 @@ public class VocabularyDao extends BaseAssetDao<Vocabulary>
@Autowired
public VocabularyDao(EntityManager em, Configuration config, DescriptorFactory descriptorFactory,
VocabularyContextMapper contextMapper, ApplicationContext context,
ChangeTrackingContextResolver changeTrackingContextResolver) {
ChangeTrackingContextResolver changeTrackingContextResolver, ChangeRecordDao changeRecordDao) {
super(Vocabulary.class, em, config.getPersistence(), descriptorFactory);
this.contextMapper = contextMapper;
refreshLastModified();
this.context = context;
this.changeTrackingContextResolver = changeTrackingContextResolver;
this.changeRecordDao = changeRecordDao;
}

@Override
Expand Down Expand Up @@ -408,88 +409,15 @@ public List<AggregatedChangeInfo> 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<AbstractChangeRecord> getDetailedHistoryOfContent(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) {
public List<AbstractChangeRecord> getDetailedHistoryOfContent(Vocabulary vocabulary, ChangeRecordFilterDto filter, Pageable pageReq) {
Objects.requireNonNull(vocabulary);
return createDetailedContentChangesQuery(vocabulary, filter, pageReq).getResultList();
}

private TypedQuery<AbstractChangeRecord> createDetailedContentChangesQuery(Vocabulary vocabulary, VocabularyContentChangeFilterDto filter, Pageable pageReq) {
TypedQuery<AbstractChangeRecord> query = em.createNativeQuery("""
SELECT DISTINCT ?record WHERE {
""" + /* Select anything from change context */ """
GRAPH ?changeContext {
?record a ?changeRecord .
}
""" + /* The record should be a subclass of "zmena" */ """
?changeRecord ?subClassOf+ ?zmena .
?record ?relatesTo ?term ;
?hasTime ?timestamp ;
?hasAuthor ?author .
""" + /* Get author's name */ """
?author ?hasFirstName ?firstName ;
?hasLastName ?lastName .
BIND(CONCAT(?firstName, " ", ?lastName) as ?authorFullName)
""" + /* When its update record, there will be a changed attribute */ """
OPTIONAL {
?record ?hasChangedAttribute ?attribute .
?attribute ?hasRdfsLabel ?changedAttributeName .
}
""" + /* Get term's name (but the term might have been already deleted) */ """
OPTIONAL {
?term a ?termType ;
?hasLabel ?label .
}
""" + /* then try to get the label from (delete) record */ """
OPTIONAL {
?record ?hasRdfsLabel ?label .
}
""" + /* When label is still not bound, the term was probably deleted, find the delete record and get the label from it */ """
OPTIONAL {
FILTER(!BOUND(?label)) .
?deleteRecord a <http://onto.fel.cvut.cz/ontologies/slovník/agendový/popis-dat/pojem/smazání-entity>;
<http://onto.fel.cvut.cz/ontologies/slovník/agendový/popis-dat/pojem/má-změněnou-entitu> ?term;
<http://www.w3.org/2000/01/rdf-schema#label> ?label.
}
BIND(?termName as ?termNameVal)
BIND(?authorName as ?authorNameVal)
BIND(?attributeName as ?changedAttributeNameVal)
FILTER (!BOUND(?termNameVal) || CONTAINS(LCASE(?label), LCASE(?termNameVal)))
FILTER (!BOUND(?authorNameVal) || CONTAINS(LCASE(?authorFullName), LCASE(?authorNameVal)))
FILTER (!BOUND(?changedAttributeNameVal) || CONTAINS(LCASE(?changedAttributeName), LCASE(?changedAttributeNameVal)))
} ORDER BY DESC(?timestamp) ?attribute
""", AbstractChangeRecord.class)
.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("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());
}

if(pageReq.isUnpaged()) {
return query;
}

return query.setFirstResult((int) pageReq.getOffset())
.setMaxResults(pageReq.getPageSize());
return changeRecordDao.findAllFiltered(
changeTrackingContextResolver.resolveChangeTrackingContext(vocabulary),
filter,
Optional.empty(),
Optional.of(URI.create(SKOS.CONCEPT)), // term
pageReq
);
}

private Query createContentChangesQuery(Vocabulary vocabulary) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,25 @@
import cz.cvut.kbss.jopa.model.EntityManager;
import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor;
import cz.cvut.kbss.jopa.model.query.TypedQuery;
import cz.cvut.kbss.jopa.vocabulary.RDFS;
import cz.cvut.kbss.jopa.vocabulary.SKOS;
import cz.cvut.kbss.termit.dto.filter.ChangeRecordFilterDto;
import cz.cvut.kbss.termit.exception.PersistenceException;
import cz.cvut.kbss.termit.model.Asset;
import cz.cvut.kbss.termit.model.User;
import cz.cvut.kbss.termit.model.changetracking.AbstractChangeRecord;
import cz.cvut.kbss.termit.model.util.HasIdentifier;
import cz.cvut.kbss.termit.util.Utils;
import cz.cvut.kbss.termit.util.Vocabulary;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

@Repository
Expand Down Expand Up @@ -66,13 +73,120 @@ public void persist(AbstractChangeRecord record, Asset<?> changedAsset) {
}
}

public List<AbstractChangeRecord> findAll(Asset<?> asset, ChangeRecordFilterDto filterDto) {
if (filterDto.isEmpty()) {
// there is nothing to filter, simple query can be used
return findAll(asset);
}
return findAllFiltered(contextResolver.resolveChangeTrackingContext(asset), filterDto, Optional.of(asset), Optional.empty(), Pageable.unpaged());
}

/**
* @param changeContext the context of change records
* @param filter filter parameters
* @param asset if present, only changes of the asset will be returned
* @param assetType if present, only changes related to this asset type will be returned.
*/
public List<AbstractChangeRecord> findAllFiltered(URI changeContext, ChangeRecordFilterDto filter, Optional<Asset<?>> asset, Optional<URI> assetType, Pageable pageable) {
TypedQuery<AbstractChangeRecord> query = em.createNativeQuery("""
SELECT DISTINCT ?record WHERE {
""" + /* Select anything from change context */ """
GRAPH ?changeContext {
?record a ?changeRecord .
}
""" + /* The record should be a subclass of changeType ("zmena") and have timestamp and author */ """
?changeRecord ?subClassOf+ ?changeType .
?record ?hasChangedEntity ?asset ;
?hasTime ?timestamp ;
?hasAuthor ?author .
?asset a ?assetType .
""" + /* Get author's name */ """
?author ?hasFirstName ?firstName ;
?hasLastName ?lastName .
BIND(CONCAT(?firstName, " ", ?lastName) as ?authorFullName)
""" + /* When its update record, there will be a changed attribute */ """
OPTIONAL {
?record ?hasChangedAttribute ?attribute .
?attribute ?hasRdfsLabel ?changedAttributeLabel .
}
""" + /* Get asset's name (but the asset might have been already deleted) */ """
OPTIONAL {
?asset ?hasLabel ?label .
}
OPTIONAL {
?asset ?hasRdfsLabel ?label .
}
""" + /* then try to get the label from (delete) record */ """
OPTIONAL {
?record ?hasRdfsLabel ?label .
}
""" + /* When label is still not bound, the term was probably deleted, find the delete record and get the label from it */ """
OPTIONAL {
FILTER(!BOUND(?label)) .
?deleteRecord a ?deleteRecordType;
?hasChangedEntity ?term;
?hasRdfsLabel ?label.
}
BIND(?assetLabelValue as ?assetLabel)
BIND(?authorNameValue as ?authorName)
BIND(?attributeNameValue as ?changedAttributeName)
FILTER (!BOUND(?assetLabel) || CONTAINS(LCASE(?label), LCASE(?assetLabel)))
FILTER (!BOUND(?authorName) || CONTAINS(LCASE(?authorFullName), LCASE(?authorName)))
FILTER (!BOUND(?changedAttributeName) || CONTAINS(LCASE(?changedAttributeLabel), LCASE(?changedAttributeName)))
} ORDER BY DESC(?timestamp) ?attribute
""", AbstractChangeRecord.class)
.setParameter("changeContext", changeContext)
.setParameter("subClassOf", URI.create(RDFS.SUB_CLASS_OF))
.setParameter("changeType", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_c_zmena))
.setParameter("hasChangedEntity", 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("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))
// Optional - update change record
.setParameter("hasChangedAttribute", URI.create(cz.cvut.kbss.termit.util.Vocabulary.s_p_ma_zmeneny_atribut))
.setParameter("hasRdfsLabel", URI.create(RDFS.LABEL))
// Optional -
.setParameter("hasLabel", URI.create(SKOS.PREF_LABEL))

// Optional asset label
.setParameter("deleteRecordType", Vocabulary.s_c_smazani_entity);

if(asset.isPresent() && asset.get().getUri() != null) {
query = query.setParameter("asset", asset.get().getUri());
} else if (assetType.isPresent()) {
query = query.setParameter("assetType", assetType.get());
}


if(!Utils.isBlank(filter.getAssetLabel())) {
query = query.setParameter("assetLabelValue", filter.getAssetLabel().trim());
}
if (!Utils.isBlank(filter.getAuthorName())) {
query = query.setParameter("authorNameValue", filter.getAuthorName().trim());
}
if (filter.getChangeType() != null) {
query = query.setParameter("changeRecord", filter.getChangeType());
}
if (!Utils.isBlank(filter.getChangedAttributeName())) {
query = query.setParameter("attributeNameValue", filter.getChangedAttributeName().trim());
}

if(pageable.isUnpaged()) {
return query.getResultList();
}

return query.setFirstResult((int) pageable.getOffset())
.setMaxResults(pageable.getPageSize()).getResultList();
}

/**
* Finds all change records to the specified asset.
*
* @param asset The changed asset
* @return List of change records ordered by timestamp (descending)
*/
public List<AbstractChangeRecord> findAll(HasIdentifier asset) {
public List<AbstractChangeRecord> findAll(Asset<?> asset) {
Objects.requireNonNull(asset);
try {
final Descriptor descriptor = new EntityDescriptor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package cz.cvut.kbss.termit.rest;

import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.termit.dto.filter.ChangeRecordFilterDto;
import cz.cvut.kbss.termit.exception.TermItException;
import cz.cvut.kbss.termit.model.TextAnalysisRecord;
import cz.cvut.kbss.termit.model.changetracking.AbstractChangeRecord;
Expand Down Expand Up @@ -360,7 +361,7 @@ public List<AbstractChangeRecord> getHistory(
required = false) Optional<String> namespace) {
final Resource resource = resourceService
.getReference(resolveIdentifier(resourceNamespace(namespace), localName));
return resourceService.getChanges(resource);
return resourceService.getChanges(resource, new ChangeRecordFilterDto()); // TODO: filter dto
}

/**
Expand Down
Loading

0 comments on commit c3b4cfd

Please sign in to comment.