Skip to content

Commit

Permalink
Merge pull request #168 from kbss-cvut/163-vocabulary-iri-context-iri…
Browse files Browse the repository at this point in the history
…-different

163 vocabulary iri context iri different
  • Loading branch information
ledsoft committed Aug 2, 2022
2 parents 7430bde + deedb6f commit 207706e
Show file tree
Hide file tree
Showing 36 changed files with 445 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cz.cvut.kbss.termit.event;

import org.springframework.context.ApplicationEvent;

/**
* Indicates that a vocabulary has been created.
*/
public class VocabularyCreatedEvent extends ApplicationEvent {

public VocabularyCreatedEvent(Object source) {
super(source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cz.cvut.kbss.termit.exception;

/**
* Indicates that a vocabulary has been found in multiple repository contexts, and it was not possible to determine
* which one to use.
*/
public class AmbiguousVocabularyContextException extends TermItException {

public AmbiguousVocabularyContextException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cz.cvut.kbss.termit.persistence.context;

import cz.cvut.kbss.jopa.model.EntityManager;
import cz.cvut.kbss.termit.event.EvictCacheEvent;
import cz.cvut.kbss.termit.event.VocabularyCreatedEvent;
import cz.cvut.kbss.termit.exception.AmbiguousVocabularyContextException;
import cz.cvut.kbss.termit.util.Vocabulary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cz.cvut.kbss.termit.util.Utils.uriToString;

/**
* Caching implementation of the {@link VocabularyContextMapper}.
* <p>
* Context map is loaded on startup and reloaded every time a vocabulary is created.
*/
@Component
@Profile("!no-cache")
public class CachingVocabularyContextMapper implements VocabularyContextMapper {

private static final Logger LOG = LoggerFactory.getLogger(CachingVocabularyContextMapper.class);

private final EntityManager em;

private Map<URI, List<URI>> contexts;

public CachingVocabularyContextMapper(EntityManager em) {
this.em = em;
}

/**
* Loads vocabulary context info into memory (cache).
*/
@EventListener(value = {VocabularyCreatedEvent.class, EvictCacheEvent.class, ContextRefreshedEvent.class})
public void load() {
this.contexts = new HashMap<>();
em.createNativeQuery("SELECT ?v ?g WHERE { GRAPH ?g { ?v a ?type . } }")
.setParameter("type", URI.create(Vocabulary.s_c_slovnik))
.getResultStream().forEach(row -> {
assert row instanceof Object[];
assert ((Object[]) row).length == 2;
final Object[] bindingSet = (Object[]) row;
final List<URI> ctx = contexts.computeIfAbsent((URI) bindingSet[0], (k) -> new ArrayList<>());
ctx.add((URI) bindingSet[1]);
});
}

/**
* Gets identifier of the repository context in which vocabulary with the specified identifier is stored.
*
* @param vocabularyUri Vocabulary identifier
* @return Repository context identifier
*/
public URI getVocabularyContext(URI vocabularyUri) {
if (!contexts.containsKey(vocabularyUri)) {
LOG.debug("No context mapped for vocabulary {}, returning the vocabulary IRI as context identifier.",
uriToString(vocabularyUri));
return vocabularyUri;
}
final List<URI> vocabularyContexts = contexts.get(vocabularyUri);
if (vocabularyContexts.size() > 1) {
throw new AmbiguousVocabularyContextException(
"Multiple repository contexts found for vocabulary " + uriToString(vocabularyUri));
}
return vocabularyContexts.get(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cz.cvut.kbss.termit.persistence.context;

import cz.cvut.kbss.jopa.exceptions.NoResultException;
import cz.cvut.kbss.jopa.exceptions.NoUniqueResultException;
import cz.cvut.kbss.jopa.model.EntityManager;
import cz.cvut.kbss.termit.exception.AmbiguousVocabularyContextException;
import cz.cvut.kbss.termit.util.Vocabulary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

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

import static cz.cvut.kbss.termit.util.Utils.uriToString;

/**
* Default implementation of the context mapper resolves vocabulary context on every call.
* <p>
* This incurs a performance penalty of executing a simple query, but does not suffer from potentially stale cache
* data.
*/
@Component
@Profile("no-cache")
public class DefaultVocabularyContextMapper implements VocabularyContextMapper {

private static final Logger LOG = LoggerFactory.getLogger(DefaultVocabularyContextMapper.class);

private final EntityManager em;

public DefaultVocabularyContextMapper(EntityManager em) {
this.em = em;
}

@Override
public URI getVocabularyContext(URI vocabularyUri) {
Objects.requireNonNull(vocabularyUri);
try {
return em.createNativeQuery("SELECT ?g WHERE { GRAPH ?g { ?vocabulary a ?type . } }", URI.class)
.setParameter("type", URI.create(Vocabulary.s_c_slovnik))
.setParameter("vocabulary", vocabularyUri)
.getSingleResult();
} catch (NoResultException e) {
LOG.debug("No context mapped for vocabulary {}, returning the vocabulary IRI as context identifier.",
uriToString(vocabularyUri));
return vocabularyUri;
} catch (NoUniqueResultException e) {
throw new AmbiguousVocabularyContextException(
"Multiple repository contexts found for vocabulary " + uriToString(vocabularyUri));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
package cz.cvut.kbss.termit.persistence;
package cz.cvut.kbss.termit.persistence.context;

import cz.cvut.kbss.jopa.model.EntityManagerFactory;
import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
Expand All @@ -37,9 +37,12 @@ public class DescriptorFactory {

private final EntityManagerFactory emf;

private final VocabularyContextMapper contextMapper;

@Autowired
public DescriptorFactory(EntityManagerFactory emf) {
public DescriptorFactory(EntityManagerFactory emf, VocabularyContextMapper contextMapper) {
this.emf = emf;
this.contextMapper = contextMapper;
}

/**
Expand All @@ -58,9 +61,9 @@ public Descriptor vocabularyDescriptor(Vocabulary vocabulary) {
return vocabularyDescriptor(vocabulary.getUri());
}

private static EntityDescriptor assetDescriptor(URI vocabularyUri) {
private EntityDescriptor assetDescriptor(URI vocabularyUri) {
Objects.requireNonNull(vocabularyUri);
return new EntityDescriptor(vocabularyUri);
return new EntityDescriptor(contextMapper.getVocabularyContext(vocabularyUri));
}

public <T> FieldSpecification<? super T, ?> fieldSpec(Class<T> entityCls, String attribute) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cz.cvut.kbss.termit.persistence.context;

import cz.cvut.kbss.termit.model.Vocabulary;

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

/**
* Maps vocabularies to repository contexts in which they are stored.
*/
public interface VocabularyContextMapper {

/**
* Gets identifier of the repository context in which the specified vocabulary is stored.
*
* @param vocabulary Vocabulary whose context to retrieve
* @return Repository context identifier
*/
default URI getVocabularyContext(Vocabulary vocabulary) {
Objects.requireNonNull(vocabulary);
return getVocabularyContext(vocabulary.getUri());
}

/**
* Gets identifier of the repository context in which vocabulary with the specified identifier is stored.
* <p>
* If the vocabulary does not exist yet (and thus has no repository context), the vocabulary identifier is returned
* as context.
*
* @param vocabularyUri Vocabulary identifier
* @return Repository context identifier
* @throws cz.cvut.kbss.termit.exception.AmbiguousVocabularyContextException In case multiple contexts for a
* vocabulary are found
*/
URI getVocabularyContext(URI vocabularyUri);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import cz.cvut.kbss.termit.model.Asset;
import cz.cvut.kbss.termit.model.User;
import cz.cvut.kbss.termit.model.comment.Comment;
import cz.cvut.kbss.termit.persistence.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.util.Vocabulary;
import cz.cvut.kbss.termit.util.Configuration.Persistence;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import cz.cvut.kbss.termit.model.resource.Document;
import cz.cvut.kbss.termit.model.resource.File;
import cz.cvut.kbss.termit.model.resource.Resource;
import cz.cvut.kbss.termit.persistence.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.util.Configuration;
import cz.cvut.kbss.termit.util.Vocabulary;
import org.springframework.context.event.EventListener;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import cz.cvut.kbss.termit.model.Term;
import cz.cvut.kbss.termit.model.Vocabulary;
import cz.cvut.kbss.termit.model.util.HasIdentifier;
import cz.cvut.kbss.termit.persistence.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.dao.util.Cache;
import cz.cvut.kbss.termit.persistence.dao.util.SparqlResultToTermInfoMapper;
import cz.cvut.kbss.termit.persistence.snapshot.AssetSnapshotLoader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import cz.cvut.kbss.termit.model.Glossary;
import cz.cvut.kbss.termit.model.Vocabulary;
import cz.cvut.kbss.termit.model.validation.ValidationResult;
import cz.cvut.kbss.termit.persistence.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.snapshot.AssetSnapshotLoader;
import cz.cvut.kbss.termit.persistence.validation.VocabularyContentValidator;
import cz.cvut.kbss.termit.service.snapshot.SnapshotProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import cz.cvut.kbss.termit.model.Asset;
import cz.cvut.kbss.termit.model.User;
import cz.cvut.kbss.termit.model.comment.Comment;
import cz.cvut.kbss.termit.persistence.DescriptorFactory;
import cz.cvut.kbss.termit.persistence.context.DescriptorFactory;
import cz.cvut.kbss.termit.util.Configuration;
import cz.cvut.kbss.termit.util.Constants;
import cz.cvut.kbss.termit.util.Utils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public T getRequiredReference(URI id) {
private Class<T> resolveGenericType() {
// Adapted from https://gist.github.com/yunspace/930d4d40a787a1f6a7d1
final List<ResolvedType> typeParameters =
new TypeResolver().resolve(this.getClass()).typeParametersFor(BaseRepositoryService.class);
new TypeResolver().resolve(this.getClass()).typeParametersFor(BaseRepositoryService.class);
assert typeParameters.size() == 1;
return (Class<T>) typeParameters.get(0).getErasedType();
}
Expand All @@ -165,6 +165,7 @@ public void persist(@NonNull T instance) {
Objects.requireNonNull(instance);
prePersist(instance);
getPrimaryDao().persist(instance);
postPersist(instance);
}

/**
Expand All @@ -178,6 +179,15 @@ protected void prePersist(@NonNull T instance) {
validate(instance);
}

/**
* Override this method to plug custom behavior into the transactional cycle of {@link #persist(HasIdentifier)}.
*
* @param instance The persisted instance, not {@code null}
*/
protected void postPersist(@NonNull T instance) {
// Do nothing
}

/**
* Merges the specified updated instance into the repository.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import cz.cvut.kbss.termit.dto.AggregatedChangeInfo;
import cz.cvut.kbss.termit.dto.Snapshot;
import cz.cvut.kbss.termit.dto.listing.TermDto;
import cz.cvut.kbss.termit.event.VocabularyCreatedEvent;
import cz.cvut.kbss.termit.exception.AssetRemovalException;
import cz.cvut.kbss.termit.exception.NotFoundException;
import cz.cvut.kbss.termit.exception.importing.VocabularyImportException;
Expand Down Expand Up @@ -31,6 +32,8 @@
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
Expand All @@ -46,7 +49,8 @@

@CacheConfig(cacheNames = "vocabularies")
@Service
public class VocabularyRepositoryService extends BaseAssetRepositoryService<Vocabulary> implements VocabularyService {
public class VocabularyRepositoryService extends BaseAssetRepositoryService<Vocabulary>
implements ApplicationEventPublisherAware, VocabularyService {

private static final Logger LOG = LoggerFactory.getLogger(VocabularyRepositoryService.class);

Expand All @@ -62,6 +66,8 @@ public class VocabularyRepositoryService extends BaseAssetRepositoryService<Voca

private final ApplicationContext context;

private ApplicationEventPublisher eventPublisher;

@Autowired
public VocabularyRepositoryService(ApplicationContext context, VocabularyDao vocabularyDao,
IdentifierResolver idResolver,
Expand Down Expand Up @@ -126,6 +132,11 @@ private void initGlossaryAndModel(Vocabulary vocabulary) {
}
}

@Override
protected void postPersist(Vocabulary instance) {
eventPublisher.publishEvent(new VocabularyCreatedEvent(instance));
}

@Override
protected void preUpdate(Vocabulary instance) {
super.preUpdate(instance);
Expand Down Expand Up @@ -270,7 +281,9 @@ public Integer getTermCount(Vocabulary vocabulary) {

@Override
public Snapshot createSnapshot(Vocabulary vocabulary) {
return getSnapshotCreator().createSnapshot(vocabulary);
final Snapshot s = getSnapshotCreator().createSnapshot(vocabulary);
eventPublisher.publishEvent(new VocabularyCreatedEvent(s));
return s;
}

private SnapshotCreator getSnapshotCreator() {
Expand All @@ -287,4 +300,9 @@ public Vocabulary findVersionValidAt(Vocabulary vocabulary, Instant at) {
return vocabularyDao.findVersionValidAt(vocabulary, at)
.orElseThrow(() -> new NotFoundException("No version valid at " + at + " exists."));
}

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public class TestConfig {
@Autowired
private Configuration configuration;

// @Bean
// @Primary
public Configuration configuration() {
return configuration;
}
Expand Down
Loading

0 comments on commit 207706e

Please sign in to comment.