Commit 17fe1ff5 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '140-large-controlledvocabulary-terms-lists' into 'master'

Resolve "ControlledVocabulary with long list of terms"

Closes #140

See merge request !132
parents 2e7d5ea7 25be8ece
/*
* Copyright 2017 Global Crop Diversity Trust
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -36,6 +36,7 @@ import org.genesys.blocks.model.UuidModel;
import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.common.model.Partner;
import org.genesys.common.model.SelfCleaning;
import org.hibernate.annotations.ColumnDefault;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
......@@ -100,6 +101,10 @@ public class ControlledVocabulary extends UuidModel implements Publishable, Self
@OrderColumn(name = "idx")
private List<VocabularyTerm> terms;
/** Number of terms in the vocabulary */
@ColumnDefault("0")
private int termCount;
/** The owner. */
@ManyToOne(cascade = {}, optional = false)
@JoinColumn(name = "partnerId", updatable = false)
......@@ -277,5 +282,22 @@ public class ControlledVocabulary extends UuidModel implements Publishable, Self
public void setTerms(final List<VocabularyTerm> terms) {
this.terms = terms;
}
/**
* Gets the term count.
*
* @return the term count
*/
public int getTermCount() {
return termCount;
}
/**
* Sets the term count.
*
* @param termCount the new term count
*/
public void setTermCount(int termCount) {
this.termCount = termCount;
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -20,6 +20,7 @@ import java.util.UUID;
import org.genesys.catalog.model.vocab.ControlledVocabulary;
import org.genesys.catalog.model.vocab.VocabularyTerm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
......@@ -69,4 +70,14 @@ public interface ControlledVocabularyRepository extends JpaRepository<Controlled
*/
@Query("select vt from ControlledVocabulary cv inner join cv.terms as vt where cv.uuid = ?1 and (vt.code like concat(?2, '%') or vt.title like concat(?2, '%') or vt.description like concat(?2, '%'))")
List<VocabularyTerm> autocompleteVocabularyTerm(UUID vocabularyUuid, String text, Pageable page);
/**
* List vocabulary terms.
*
* @param vocabulary the vocabulary
* @param page the page
* @return the page
*/
@Query("select vt from ControlledVocabulary cv inner join cv.terms as vt where cv = ?1")
Page<VocabularyTerm> listVocabularyTerms(ControlledVocabulary vocabulary, Pageable page);
}
......@@ -111,4 +111,15 @@ public interface VocabularyService {
*/
List<VocabularyTerm> autocompleteTerms(UUID vocabularyUuid, String text);
/**
* List terms in the vocabulary
*
* @param vocabulary
* @param page
* @return
*/
// @PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#vocabulary,
// 'read')")
Page<VocabularyTerm> listTerms(ControlledVocabulary vocabulary, Pageable page);
}
......@@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.genesys.catalog.exceptions.InvalidApiUsageException;
import org.genesys.catalog.exceptions.NotFoundElement;
import org.genesys.catalog.model.vocab.ControlledVocabulary;
......@@ -59,6 +61,9 @@ public class VocabularyServiceImpl implements VocabularyService {
@Autowired
private PartnerService partnerService;
@Autowired
private EntityManager entityManager;
@Override
@Transactional
public ControlledVocabulary createVocabulary(final ControlledVocabulary input) {
......@@ -78,14 +83,15 @@ public class VocabularyServiceImpl implements VocabularyService {
} else {
controlledVocabulary.setTerms(new ArrayList<>());
}
return lazyLoad(vocabRepository.save(controlledVocabulary));
controlledVocabulary.setTermCount(controlledVocabulary.getTerms().size());
return vocabRepository.save(controlledVocabulary);
}
/**
* Persist or update terms in descriptor itself. It updates descriptor's own
* terms List
* Persist or update terms in vocabulary itself. It updates vocabulary own terms
* List
*
* @param descriptor
* @param vocabulary
*/
protected void updateTerms(final ControlledVocabulary vocabulary) {
List<VocabularyTerm> terms = vocabulary.getTerms();
......@@ -102,21 +108,28 @@ public class VocabularyServiceImpl implements VocabularyService {
return lazyLoad(vocabulary);
}
@Override
public ControlledVocabulary getVocabulary(UUID uuid, int version) {
return lazyLoad(vocabRepository.getByUuidAndVersion(uuid, version));
}
protected ControlledVocabulary lazyLoad(final ControlledVocabulary vocabulary) {
if (vocabulary != null) {
vocabulary.getTerms().size();
// Do not load all terms, only first 50
if (vocabulary.getTerms() != null) {
List<VocabularyTerm> sublist = new ArrayList<>(vocabulary.getTerms().subList(0, Math.min(vocabulary.getTerms().size(), 50)));
vocabulary.setTerms(sublist);
}
if (vocabulary.getOwner() != null) {
vocabulary.getOwner().getId();
}
entityManager.detach(vocabulary);
}
return vocabulary;
}
@Override
public ControlledVocabulary getVocabulary(UUID uuid, int version) {
return lazyLoad(vocabRepository.getByUuidAndVersion(uuid, version));
}
@Override
@Transactional
public ControlledVocabulary updateVocabulary(final ControlledVocabulary input) {
......@@ -124,7 +137,7 @@ public class VocabularyServiceImpl implements VocabularyService {
throw new InvalidDataAccessApiUsageException("No uuid or version provided");
}
final ControlledVocabulary vocabulary = getVocabulary(input.getUuid(), input.getVersion());
final ControlledVocabulary vocabulary = vocabRepository.getByUuidAndVersion(input.getUuid(), input.getVersion());
if (vocabulary == null) {
throw new ConcurrencyFailureException("Record with that version doesn't exist");
......@@ -134,8 +147,6 @@ public class VocabularyServiceImpl implements VocabularyService {
throw new DataAccessResourceFailureException("Published vocabulary can't be updated");
}
updateTerms(input);
vocabulary.setDescription(input.getDescription());
vocabulary.setPublished(input.isPublished());
vocabulary.setPublisher(input.getPublisher());
......@@ -147,20 +158,22 @@ public class VocabularyServiceImpl implements VocabularyService {
if (input.getTerms() != null) {
List<VocabularyTerm> terms = vocabulary.getTerms();
if (terms == null) {
vocabulary.setTerms(input.getTerms());
} else {
terms.clear();
terms.addAll(input.getTerms());
vocabulary.setTerms(new ArrayList<>());
}
terms.clear();
terms.addAll(input.getTerms());
updateTerms(vocabulary);
vocabulary.setTermCount(terms.size());
}
return lazyLoad(vocabRepository.save(vocabulary));
return vocabRepository.save(vocabulary);
}
@Override
@Transactional
public ControlledVocabulary autoUpdateOrCreateVocabulary(final UUID uuid, final ControlledVocabulary input) {
final ControlledVocabulary oldVocabulary = getVocabulary(uuid);
final ControlledVocabulary oldVocabulary = vocabRepository.getByUuid(uuid);
if (oldVocabulary != null) {
LOG.info("Updating {} vocabulary with {} terms", oldVocabulary.getTitle(), input.getTerms().size());
......@@ -174,12 +187,12 @@ public class VocabularyServiceImpl implements VocabularyService {
terms.clear();
terms.addAll(input.getTerms());
}
oldVocabulary.setTermCount(oldVocabulary.getTerms().size());
}
return lazyLoad(vocabRepository.save(oldVocabulary));
return vocabRepository.save(oldVocabulary);
} else {
updateTerms(input);
input.setUuid(uuid);
input.setOwner(partnerService.getPrimaryPartner());
LOG.info("Creating {} vocabulary with {} terms", input.getTitle(), input.getTerms().size());
......@@ -201,7 +214,7 @@ public class VocabularyServiceImpl implements VocabularyService {
} else if (input != null && input.getTerms() != null) {
LOG.info("Matching against {} existing terms", existing.size());
// match existing codes
input.getTerms().forEach(inputTerm -> {
// only when there's a code
......@@ -218,7 +231,7 @@ public class VocabularyServiceImpl implements VocabularyService {
.findFirst().orElse(null));
if (inputTerm.getId() == null) {
LOG.info("New vocabulary term {}", inputTerm);
LOG.debug("New vocabulary term {}", inputTerm);
}
}
});
......@@ -253,4 +266,9 @@ public class VocabularyServiceImpl implements VocabularyService {
public List<VocabularyTerm> autocompleteTerms(UUID vocabularyUuid, String text) {
return vocabRepository.autocompleteVocabularyTerm(vocabularyUuid, text, new PageRequest(0, 20, new Sort("vt.code")));
}
@Override
public Page<VocabularyTerm> listTerms(ControlledVocabulary vocabulary, Pageable page) {
return vocabRepository.listVocabularyTerms(vocabulary, page);
}
}
......@@ -15,10 +15,7 @@
*/
package org.genesys.catalog.service;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
......@@ -37,6 +34,8 @@ import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import com.google.common.collect.Lists;
......@@ -185,6 +184,7 @@ public class ControlledVocabularyTest extends CatalogServiceTest {
vocab1.setTitle(VOCABULARY_TITLE_2);
vocab1.setPublisher("PUBLISHER");
result = vocabularyService.updateVocabulary(vocab1);
result.setTerms(null);
assertThat(result.getUuid(), is(uuid));
assertThat(result.getTitle(), is(VOCABULARY_TITLE_2));
assertThat(result.getDescription(), is(nullValue()));
......@@ -193,6 +193,7 @@ public class ControlledVocabularyTest extends CatalogServiceTest {
result.setTitle(VOCABULARY_TITLE_1);
result.setDescription(VOCABULARY_DESC_1);
result = vocabularyService.updateVocabulary(result);
result.setTerms(null);
assertThat(result.getUuid(), is(uuid));
assertThat(result.getTitle(), is(VOCABULARY_TITLE_1));
assertThat(result.getDescription(), is(VOCABULARY_DESC_1));
......@@ -327,7 +328,7 @@ public class ControlledVocabularyTest extends CatalogServiceTest {
/*@formatter:on */
));
vocabularyService.createVocabulary(vocab1);
vocab1 = vocabularyService.createVocabulary(vocab1);
assertThat(vocabularyService.getVocabularyTerm(uuid, "a" ), not(nullValue()));
assertThat(vocabularyService.getVocabularyTerm(uuid, "aaa"), not(nullValue()));
......@@ -360,4 +361,66 @@ public class ControlledVocabularyTest extends CatalogServiceTest {
assertThat(vocabularyService.autocompleteTerms(uuid, "aa"), hasSize(2));
assertThat(vocabularyService.autocompleteTerms(uuid, "aaa"), hasSize(1));
}
@Test
public void testOver50Terms() {
final UUID uuid = UUID.randomUUID();
ControlledVocabulary vocab1 = new ControlledVocabulary();
vocab1.setUuid(uuid);
vocab1.setTitle(VOCABULARY_TITLE_1);
vocab1.setOwner(partner);
ArrayList<VocabularyTerm> terms = Lists.newArrayList();
vocab1.setTerms(terms);
for (int i=0; i<100; i++) {
terms.add(VocabularyTerm.fromData("a"+i, "a" + i));
}
vocabularyService.createVocabulary(vocab1);
ControlledVocabulary result = vocabularyService.getVocabulary(uuid);
assertThat(result, not(nullValue()));
assertThat(result.getTermCount(), greaterThan(50));
assertThat(result.getTermCount(), is(100));
assertThat(result.getTerms().size(), is(50));
// another 100
for (int i=100; i<200; i++) {
terms.add(VocabularyTerm.fromData("a"+i, "a" + i));
}
result.setTerms(terms);
vocabularyService.updateVocabulary(result);
result = vocabularyService.getVocabulary(uuid);
assertThat(result, not(nullValue()));
assertThat(result.getTermCount(), is(200));
assertThat(result.getTerms().size(), is(50));
}
@Test
public void testListOver50Terms() {
final UUID uuid = UUID.randomUUID();
ControlledVocabulary vocab1 = new ControlledVocabulary();
vocab1.setUuid(uuid);
vocab1.setTitle(VOCABULARY_TITLE_1);
vocab1.setOwner(partner);
ArrayList<VocabularyTerm> terms = Lists.newArrayList();
vocab1.setTerms(terms);
for (int i=0; i<200; i++) {
terms.add(VocabularyTerm.fromData("a"+i, "a" + i));
}
vocabularyService.createVocabulary(vocab1);
ControlledVocabulary result = vocabularyService.getVocabulary(uuid);
Page<VocabularyTerm> paged = vocabularyService.listTerms(result, new PageRequest(0, 100));
assertThat(paged.getContent(), not(nullValue()));
assertThat(paged.getTotalElements(), is(200l));
assertThat(paged.getNumber(), is(0));
assertThat(paged.getSize(), is(100));
paged = vocabularyService.listTerms(result, new PageRequest(1, 100));
assertThat(paged.getContent(), not(nullValue()));
assertThat(paged.getTotalElements(), is(200l));
assertThat(paged.getNumber(), is(1));
assertThat(paged.getSize(), is(100));
}
}
......@@ -61,6 +61,14 @@ public class VocabularyController {
return vocabularyService.autocompleteTerms(uuid, like);
}
@PostMapping(value = "/{UUID}/terms")
public Page<VocabularyTerm> listTerms(@PathVariable("UUID") final UUID uuid, @RequestParam(name = "p", required = false, defaultValue = "0") final int page,
@RequestParam(name = "l", required = false, defaultValue = "50") final int pageSize,
@RequestParam(name = "d", required = false, defaultValue = "ASC") final Sort.Direction direction,
@RequestParam(name = "s", required = false, defaultValue = "id") final String[] sort) {
return vocabularyService.listTerms(vocabularyService.getVocabulary(uuid), new PageRequest(page, Integer.min(pageSize, 100), direction, sort));
}
@DeleteMapping(value = "/{UUID},{version}")
public ControlledVocabulary delete(@PathVariable("UUID") final UUID uuid, @PathVariable("version") final int version) {
return vocabularyService.deleteVocabulary(vocabularyService.getVocabulary(uuid, version));
......
......@@ -3480,3 +3480,18 @@ databaseChangeLog:
- renameTable:
newTableName: partner_wiews
oldTableName: partnerwiews
- changeSet:
id: 1517506916000-1
author: mobreza
changes:
- addColumn:
columns:
- column:
defaultValueNumeric: 0
constraints:
nullable: false
name: term_count
type: INT
tableName: controlled_vocabulary
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment