TaxonomyServiceImpl.java
/*
* Copyright 2020 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.server.service.impl;
import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.lang3.StringUtils;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.genesys.QTaxonomy2;
import org.genesys.server.model.genesys.Taxonomy2;
import org.genesys.server.model.grin.TaxonomySpecies;
import org.genesys.server.persistence.AccessionRepository;
import org.genesys.server.persistence.Taxonomy2Repository;
import org.genesys.server.persistence.grin.TaxonomyGenusRepository;
import org.genesys.server.persistence.grin.TaxonomySpeciesRepository;
import org.genesys.server.service.ElasticsearchService;
import org.genesys.server.service.TaxonomyService;
import org.genesys.server.service.filter.TaxonomyExtraFilter;
import org.genesys.server.service.filter.TaxonomyFilter;
import org.genesys.server.service.filter.TaxonomySpeciesFilter;
import org.genesys.spring.TransactionHelper;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
@Service
@Transactional(readOnly = true)
@Slf4j
public class TaxonomyServiceImpl implements TaxonomyService {
@Autowired
private Taxonomy2Repository taxonomy2Repository;
@Autowired
private TaxonomySpeciesRepository grinSpeciesRepository;
@Autowired
private TaxonomyGenusRepository grinGenusRepository;
@Autowired
private AccessionRepository accessionRepository;
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Autowired(required = false)
private ElasticsearchService elasticsearchService;
@PersistenceContext
private EntityManager entityManager;
@Override
public Taxonomy2 get(Long id) {
return taxonomy2Repository.findById(id).orElse(null);
}
@Override
public List<String> autocompleteGenus(String term) {
List<String> strings;
strings = taxonomy2Repository.autocompleteGenus(term + "%", PageRequest.of(0, 200));
return strings;
}
@Override
public List<String> autocompleteSpecies(String term, List<String> genus) {
List<String> strings;
if (!genus.isEmpty()) {
strings = taxonomy2Repository.autocompleteSpeciesByGenus("%" + term + "%", genus, PageRequest.of(0, 10));
} else {
strings = taxonomy2Repository.autocompleteSpecies("%" + term + "%", PageRequest.of(0, 10));
}
return strings;
}
@Override
public List<String> autocompleteSubtaxa(String term, List<String> genus, List<String> species) {
List<String> strings;
if (!genus.isEmpty() && !species.isEmpty()) {
log.debug("Genus={} sp={}", genus, species);
strings = taxonomy2Repository.autocompleteSubtaxaByGenusAndSpecies("%" + term + "%", genus, species, PageRequest.of(0, 10));
} else if (!species.isEmpty()) {
log.debug("sp={}", species);
strings = taxonomy2Repository.autocompleteSubtaxaBySpecies("%" + term + "%", species, PageRequest.of(0, 10));
} else if (!genus.isEmpty()) {
log.debug("Genus={}", genus);
strings = taxonomy2Repository.autocompleteSubtaxaByGenus("%" + term + "%", genus, PageRequest.of(0, 10));
} else {
strings = taxonomy2Repository.autocompleteSubtaxa("%" + term + "%", PageRequest.of(0, 10));
}
return strings;
}
@Override
public List<String> autocompleteTaxonomy(String term) {
return taxonomy2Repository.autocompleteTaxonomy("%" + term + "%", PageRequest.of(0, 10));
}
@Override
@Cacheable(value = "hibernate.org.genesys.server.model.impl.Taxonomy2.fullname", key = "#genus + '-' + #species + '-' + #spAuthor + '-' + #subtaxa + '-' + #subtAuthor", unless = "#result == null")
public Taxonomy2 find(String genus, String species, String spAuthor, String subtaxa, String subtAuthor) {
return taxonomy2Repository.findByGenusAndSpeciesAndSpAuthorAndSubtaxaAndSubtAuthor(genus, species, spAuthor, subtaxa, subtAuthor);
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public Taxonomy2 internalEnsure(String genus, String species, String spAuthor, String subtaxa, String subtAuthor) throws InterruptedException {
Long taxSpeciesId = null, taxGenusId = null;
// Direct species
if (subtaxa.equals("") && spAuthor.equals("") && subtAuthor.equals("")) {
if (StringUtils.equals(species, "sp.")) {
// Self
} else {
final Taxonomy2 genusTaxa = internalEnsure(genus, "sp.", "", "", "");
taxGenusId = genusTaxa.getId();
}
} else {
final Taxonomy2 speciesTaxa = internalEnsure(genus, species, "", "", "");
taxSpeciesId = speciesTaxa.getId();
taxGenusId = speciesTaxa.getTaxGenus();
}
Taxonomy2 taxonomy = null;
try {
taxonomy = find(genus, species, spAuthor, subtaxa, subtAuthor);
} catch (final Throwable e) {
log.info("Taxonomy not found: {}", e.getMessage());
}
if (taxonomy != null) {
return taxonomy;
} else {
log.info("Adding new taxonomic name: {} {} {} {} {}", genus, species, spAuthor, subtaxa, subtAuthor);
taxonomy = new Taxonomy2();
taxonomy.setGenus(genus);
taxonomy.setSpecies(species);
taxonomy.setSpAuthor(spAuthor);
taxonomy.setSubtaxa(subtaxa);
taxonomy.setSubtAuthor(subtAuthor);
taxonomy.setTaxGenus(taxGenusId);
taxonomy.setTaxSpecies(taxSpeciesId);
try {
taxonomy = taxonomy2Repository.save(taxonomy);
if (taxGenusId == null) {
taxonomy.setTaxGenus(taxonomy.getId());
taxonomy = taxonomy2Repository.save(taxonomy);
}
if (taxSpeciesId == null) {
taxonomy.setTaxSpecies(taxonomy.getId());
taxonomy = taxonomy2Repository.save(taxonomy);
}
return taxonomy;
} catch (final Throwable e) {
log.warn("Error {} :{}", e.getMessage(), taxonomy);
throw new RuntimeException(e.getMessage());
}
}
}
@Override
public long getTaxonomy2Id(String genus) {
return find(genus, "sp.", "", "", "").getId();
}
@Override
public long getTaxonomy2Id(String genus, String species) {
final Taxonomy2 tax = find(genus, species, "", "", "");
return tax.getId();
}
@Override
public Taxonomy2 get(String genus, String species) {
return find(genus, species, "", "", "");
}
@Override
public long countTaxonomy2() {
return taxonomy2Repository.count();
}
@Override
public Taxonomy2 get(String genus) {
return find(genus, "sp.", "", "", "");
}
@Override
public List<Taxonomy2> list(TaxonomyFilter filter) {
final BooleanBuilder predicate = new BooleanBuilder();
if (filter != null) {
predicate.and(filter.buildPredicate());
}
return Lists.newArrayList(taxonomy2Repository.findAll(predicate, QTaxonomy2.taxonomy2.genus.asc(), QTaxonomy2.taxonomy2.species.asc(), QTaxonomy2.taxonomy2.id.asc()));
}
@Override
public Page<Taxonomy2Info> list(TaxonomyExtraFilter filter, Pageable page) {
final BooleanBuilder predicate = new BooleanBuilder();
if (filter != null) {
predicate.and(filter.buildPredicate());
}
JPAQuery<Taxonomy2> query = jpaQueryFactory.selectFrom(QTaxonomy2.taxonomy2);
query.leftJoin(QTaxonomy2.taxonomy2.grinTaxonomySpecies());
query.leftJoin(QTaxonomy2.taxonomy2.currentTaxonomySpecies());
List<Taxonomy2> matches = query.where(predicate).offset(page.getOffset()).limit(page.getPageSize()).orderBy(
QTaxonomy2.taxonomy2.accessions.size().desc(), QTaxonomy2.taxonomy2.taxonName.asc(), QTaxonomy2.taxonomy2.id.asc()).fetch();
final Page<Taxonomy2> res = taxonomy2Repository.findAll(predicate, page);
final List<Taxonomy2Info> content = matches.stream().map(taxonomy2 -> {
Long accessionCount = accessionRepository.countByTaxonomy(taxonomy2);
return Taxonomy2Info.from(taxonomy2, accessionCount);
}).collect(Collectors.toList());
return new PageImpl<>(content, page, res.getTotalElements());
}
@Override
public Page<TaxonomySpecies> listSpecies(TaxonomySpeciesFilter filter, Pageable page) throws SearchException {
final BooleanBuilder predicate = new BooleanBuilder();
if (filter != null) {
predicate.and(filter.buildPredicate());
if (filter.isFulltextQuery()) {
return elasticsearchService.findAll(TaxonomySpecies.class, filter, page);
}
}
Page<TaxonomySpecies> res = grinSpeciesRepository.findAll(predicate, page);
// Ensure current species is loaded
res.getContent().forEach(grinSpecies -> Hibernate.initialize(grinSpecies.getCurrentTaxonomySpecies()));
return new PageImpl<>(res.getContent(), page, res.getTotalElements());
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
public Taxonomy2 setGrinSpecies(long taxonomy2Id, Long customGrinSpeciesId) {
Taxonomy2 taxonomy2 = taxonomy2Repository.findById(taxonomy2Id).orElseThrow(() -> new NotFoundElement("No such Taxonomy2"));
if (customGrinSpeciesId == null) {
taxonomy2.setGrinTaxonomySpecies(null);
taxonomy2.setCurrentTaxonomySpecies(null);
taxonomy2.setOverrideTaxonomySpecies(null);
taxonomy2.setFamily(null);
} else {
TaxonomySpecies grinSpecies = grinSpeciesRepository.findById(customGrinSpeciesId).orElseThrow(() -> new NotFoundElement("No such TaxonomySpecies"));
taxonomy2.setOverrideTaxonomySpecies(grinSpecies);
taxonomy2.setGrinTaxonomySpecies(grinSpecies);
taxonomy2.setCurrentTaxonomySpecies(grinSpecies.getCurrentTaxonomySpecies());
taxonomy2.setFamily(grinSpecies.getTaxonomyGenus().getTaxonomyFamily().getFamilyName());
}
return taxonomy2Repository.save(taxonomy2);
}
@Override
public List<String> getAllGenera() {
return taxonomy2Repository.getAllGenera();
}
@Override
public List<Long> findByGenus(List<String> genus) {
return taxonomy2Repository.findByGenus(genus);
}
@Override
@Transactional
public void cleanupTaxonomies() {
Set<BigInteger> referencedIds = taxonomy2Repository.findTaxonomyReferencedIds();
Set<Long> allIds = taxonomy2Repository.findTaxonomyIds();
for (BigInteger integer : referencedIds) {
allIds.remove(integer.longValue());
}
if (allIds.size() > 0) {
taxonomy2Repository.removeUnusedIds(allIds);
}
}
@Override
@Transactional(timeout = 50, isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED)
public Taxonomy2 ensureTaxonomy(Taxonomy2 example) {
example.sanitize();
Taxonomy2 existing = taxonomy2Repository.findByGenusAndSpeciesAndSpAuthorAndSubtaxaAndSubtAuthor(example.getGenus(), example.getSpecies(), example.getSpAuthor(), example
.getSubtaxa(), example.getSubtAuthor());
if (existing == null) {
log.debug("Adding taxonomy {} {} {} {} {}", example.getGenus(), example.getSpecies(), example.getSpAuthor(), example.getSubtaxa(), example.getSubtAuthor());
Taxonomy2 newTaxa = new Taxonomy2();
newTaxa.setGenus(example.getGenus());
newTaxa.setSpecies(example.getSpecies());
newTaxa.setSpAuthor(example.getSpAuthor());
newTaxa.setSubtaxa(example.getSubtaxa());
newTaxa.setSubtAuthor(example.getSubtAuthor());
newTaxa.sanitize();
Taxonomy2 tsP = new Taxonomy2();
tsP.setGenus(newTaxa.getGenus());
tsP.setSpecies(newTaxa.getSpecies());
tsP.sanitize();
if (tsP.equalTo(newTaxa)) {
// equal to species level
tsP.setSpecies(null);
tsP.sanitize();
// get genus id
if (!tsP.equalTo(newTaxa)) {
tsP = ensureTaxonomy(tsP);
newTaxa.setTaxGenus(tsP.getTaxGenus());
} else {
// incoming taxonomy is genus level only.
// System.err.println("What now " + newTaxa + " == " + tsP);
}
} else {
// not equal to species level, ensure taxSpecies
tsP = ensureTaxonomy(tsP);
newTaxa.setTaxGenus(tsP.getTaxGenus());
newTaxa.setTaxSpecies(tsP.getId());
}
Taxonomy2 t = taxonomy2Repository.save(newTaxa.sanitize());
// self-references
t.setTaxGenus(t.getTaxGenus());
t.setTaxSpecies(t.getTaxSpecies());
return taxonomy2Repository.save(t);
} else {
return existing;
}
}
@Override
public List<Long> findTaxonomy2GenusId(List<String> genera) {
return taxonomy2Repository.findTaxGenusId(genera);
}
@Override
public List<Long> findTaxonomy2SpeciesId(List<String> speciesNames) {
return taxonomy2Repository.findTaxSpeciesId(speciesNames);
}
@Override
public List<Long> findGrinGenusId(List<String> genera) {
return grinGenusRepository.findTaxonomyGenusId(genera);
}
@Override
public List<Long> findGrinSpeciesId(List<String> speciesNames) {
return grinSpeciesRepository.findTaxonomySpeciesId(speciesNames);
}
@Override
public List<Long> findTaxonomy2ByGrinGenus(List<String> genus) {
return taxonomy2Repository.findByGrinGenus(genus);
}
@Override
public List<Long> findTaxonomy2ByCurrentGrinGenus(List<String> genus) {
return taxonomy2Repository.findByCurrentGrinGenus(genus);
}
@Override
public List<Long> findTaxonomy2ByGrinSpecies(List<String> genusSpecies) {
return taxonomy2Repository.findByGrinGenusAndSpecies(genusSpecies);
}
@Override
public List<Long> findTaxonomy2ByCurrentGrinSpecies(List<String> genusSpecies) {
return taxonomy2Repository.findByCurrentGrinGenusAndSpecies(genusSpecies);
}
@Override
public List<Long> findTaxonomy2ByGrinNames(List<String> names) {
return taxonomy2Repository.findByGrinNames(names);
}
@Override
public List<Long> findTaxonomy2ByCurrentGrinNames(List<String> names) {
return taxonomy2Repository.findByCurrentGrinNames(names);
}
@Override
public List<Long> listTaxonomy2ByGrinId(Collection<Long> grinId) {
return taxonomy2Repository.findByGrinId(grinId);
}
@Override
public List<Long> listTaxonomy2ByCurrentGrinId(Collection<Long> grinId) {
return taxonomy2Repository.findByCurrentGrinId(grinId);
}
@Override
@Transactional
public void updateFamilyNames() {
var sqlUpdateQuery =
"update taxonomy2 t2 set t2.lastModifiedDate = now(), t2.family = " +
"(select distinct gf.family_name from grin_species gs " +
"left join grin_genus gg on gg.taxonomy_genus_id = gs.taxonomy_genus_id " +
"left join grin_family gf on gg.taxonomy_family_id = gf.taxonomy_family_id " +
"where t2.grinTaxonomySpecies = gs.taxonomy_species_id) " +
"where t2.grinTaxonomySpecies is not null;";
var updated = entityManager.createNativeQuery(sqlUpdateQuery).executeUpdate();
log.warn("Updated {} taxonomy records with GRIN family names", updated);
}
}