InstituteServiceImpl.java
/**
* Copyright 2014 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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityNotFoundException;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys.server.api.v2.MultiOp;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.Partner;
import org.genesys.server.model.impl.Country;
import org.genesys.server.model.impl.FaoInstitute;
import org.genesys.server.model.impl.FaoInstituteSetting;
import org.genesys.server.model.impl.QFaoInstitute;
import org.genesys.server.model.vocab.VocabularyTerm;
import org.genesys.server.persistence.AccessionRepository;
import org.genesys.server.persistence.FaoInstituteRepository;
import org.genesys.server.persistence.FaoInstituteSettingRepository;
import org.genesys.server.persistence.GenesysLowlevelRepository;
import org.genesys.server.service.InstituteService;
import org.genesys.server.service.filter.InstituteFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
@Service
@Transactional(readOnly = true)
@Validated
public class InstituteServiceImpl extends FilteredCRUDServiceImpl<FaoInstitute, InstituteFilter, FaoInstituteRepository> implements InstituteService {
public static final Logger LOG = LoggerFactory.getLogger(InstituteServiceImpl.class);
private final static String HIBERNATE_CACHENAME = "hibernate.org.genesys.server.model.impl.FaoInstitute";
private static final List<FaoInstitute> EMPTY_LIST = ListUtils.unmodifiableList(new ArrayList<FaoInstitute>());
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Autowired
private FaoInstituteRepository instituteRepository;
@Autowired
private FaoInstituteSettingRepository instituteSettingRepository;
// @Autowired
// private ContentService contentService;
@Autowired
@Qualifier("genesysLowlevelRepositoryCustomImpl")
private GenesysLowlevelRepository genesysLowlevelRepository;
@Autowired
private CustomAclService aclService;
@Autowired
private AccessionRepository accessionRepository;
@Override
public Page<FaoInstitute> listActive(Pageable pageable) {
return instituteRepository.listAllActive(pageable);
}
@Override
@Transactional
@CacheEvict(value = { HIBERNATE_CACHENAME }, key = "#wiewsCode")
public FaoInstitute update(final String wiewsCode, final FaoInstitute institute) {
FaoInstitute target = findInstitute(wiewsCode);
copyValues(target, institute);
instituteSettingRepository.saveAll(target.getSettings().values());
if (target.isUniqueAcceNumbs()) {
// Assure existing data has unique accenumbs
target.setUniqueAcceNumbs(0 == accessionRepository.countNonuniqueAccessionNumbers(target.getId()));
}
return instituteRepository.save(target);
}
@Override
public Page<FaoInstitute> listPGRInstitutes(Pageable pageable) {
return instituteRepository.listPGRInstitutes(pageable);
}
@Override
public long countActive() {
return instituteRepository.countActive();
}
@Override
@Cacheable(cacheNames = { HIBERNATE_CACHENAME }, key = "#wiewsCode")
public FaoInstitute getInstitute(String wiewsCode) {
return instituteRepository.findByCode(wiewsCode);
}
@Override
public FaoInstitute findInstitute(String wiewsCode) {
final FaoInstitute inst = instituteRepository.findByCode(wiewsCode);
if (inst != null) {
inst.getSettings().size();
}
return inst;
}
/**
* Returns institute if user has required permissions
*
* @param wiewsCode code
* @return institute
*/
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') or hasPermission(returnObject, 'ADMINISTRATION')")
public FaoInstitute getInstituteForEdit(final String wiewsCode) {
return instituteRepository.findByCode(wiewsCode);
}
@Override
public VocabularyTerm getInstituteTerm(String wiewsCode) {
return toVocabularyTerm(getInstitute(wiewsCode));
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
public FaoInstitute create(FaoInstitute source) {
return _lazyLoad(repository.save(source));
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#target, 'ADMINISTRATION')")
public FaoInstitute update(FaoInstitute updated, FaoInstitute target) {
return update(target.getCode(), updated);
}
@Override
public List<FaoInstitute> listByCountry(Country country) {
return instituteRepository.listByCountry(country, Sort.by("code"));
}
@Override
public List<FaoInstitute> listByCountryActive(Country country) {
return instituteRepository.findByCountryActive(country, Sort.by(Direction.DESC, "accessionCount", "code"));
}
@Override
public List<FaoInstitute> getInstitutes(Collection<String> wiewsCodes) {
if (wiewsCodes == null || wiewsCodes.size() == 0) {
return EMPTY_LIST;
}
return instituteRepository.findAllByCodes(wiewsCodes);
}
@Override
@Transactional(readOnly = false)
@CacheEvict(value = { "statistics", HIBERNATE_CACHENAME }, allEntries = true)
public MultiOp<FaoInstitute> update(final List<FaoInstitute> institutes) {
var result = new MultiOp<FaoInstitute>();
result.success = new ArrayList<FaoInstitute>(institutes.size());
institutes.forEach(institute -> {
if (institute.getId() != null && institute.isUniqueAcceNumbs()) {
long uniqueAcceNumbs = accessionRepository.countUniqueAccessionNumbers(institute.getId());
if (uniqueAcceNumbs > 0) {
long nonUniqueAcceNumbs = accessionRepository.countNonuniqueAccessionNumbers(institute.getId());
institute.setUniqueAcceNumbs(0 == nonUniqueAcceNumbs);
} else {
// Assume first insert into Genesys may contain duplicates
institute.setUniqueAcceNumbs(false);
}
}
});
result.success.addAll(instituteRepository.saveAll(institutes));
return result;
}
// @Override
// @PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER') or hasPermission(#faoInstitute, 'ADMINISTRATION')")
// @Transactional(readOnly = false)
// public Article updateAbout(FaoInstitute faoInstitute, String summary, String body, Locale locale) throws CRMException {
// return contentService.updateArticle(faoInstitute, ContentService.ENTITY_BLURB_SLUG, null, summary, body, locale);
// }
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#faoInstitute, 'ADMINISTRATION')")
@Transactional(readOnly = false)
@CacheEvict(value = { HIBERNATE_CACHENAME }, key = "#faoInstitute.code")
public void setUniqueAcceNumbs(FaoInstitute faoInstitute, boolean uniqueAcceNumbs) {
final FaoInstitute inst = instituteRepository.findById(faoInstitute.getId()).orElseThrow(EntityNotFoundException::new);
LOG.info("Setting 'uniqueAcceNumbs' to {} for {}", uniqueAcceNumbs, faoInstitute);
inst.setUniqueAcceNumbs(uniqueAcceNumbs);
if (uniqueAcceNumbs == true) {
// If set to unique, test that current data is unique!
inst.setUniqueAcceNumbs(0 == accessionRepository.countNonuniqueAccessionNumbers(inst.getId()));
}
instituteRepository.save(inst);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#faoInstitute, 'ADMINISTRATION')")
@Transactional(readOnly = false)
@CacheEvict(value = { HIBERNATE_CACHENAME }, key = "#faoInstitute.code")
public void setAllowMaterialRequests(FaoInstitute faoInstitute, boolean allowMaterialRequests) {
final FaoInstitute inst = instituteRepository.findById(faoInstitute.getId()).orElseThrow(EntityNotFoundException::new);
LOG.info("Setting 'allowMaterialRequests' to {} for {}", allowMaterialRequests, faoInstitute);
inst.setAllowMaterialRequests(allowMaterialRequests);
instituteRepository.save(inst);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#faoInstitute, 'ADMINISTRATION')")
@Transactional(readOnly = false)
@CacheEvict(value = { HIBERNATE_CACHENAME }, key = "#faoInstitute.code")
public void setCodeSGSV(FaoInstitute faoInstitute, String codeSGSV) {
final FaoInstitute inst = instituteRepository.findById(faoInstitute.getId()).orElseThrow(EntityNotFoundException::new);
LOG.info("Setting 'codeSGSV' to {} for {}", codeSGSV, faoInstitute);
inst.setCodeSGSV(codeSGSV);
instituteRepository.save(inst);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#faoInstitute, 'ADMINISTRATION')")
@Transactional(readOnly = false)
@CacheEvict(value = { HIBERNATE_CACHENAME }, key = "#faoInstitute.code")
public void updateSettings(FaoInstitute faoInstitute, Map<String, String> settings) {
final List<FaoInstituteSetting> toSave = new ArrayList<FaoInstituteSetting>();
final List<FaoInstituteSetting> toRemove = new ArrayList<FaoInstituteSetting>();
for (final var entry : settings.entrySet()) {
final String settingValue = StringUtils.defaultIfBlank(entry.getValue(), null);
FaoInstituteSetting setting = instituteSettingRepository.findByInstCodeAndSetting(faoInstitute.getCode(), entry.getKey());
if (setting == null && settingValue != null) {
setting = new FaoInstituteSetting(faoInstitute);
setting.setSetting(entry.getKey());
setting.setValue(settingValue);
toSave.add(setting);
} else if (setting != null) {
setting.setValue(settingValue);
if (settingValue == null) {
toRemove.add(setting);
} else {
toSave.add(setting);
}
}
}
instituteSettingRepository.saveAll(toSave);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Transactional(readOnly = false)
@CacheEvict(value = { "statistics", HIBERNATE_CACHENAME }, allEntries = true)
public void updateCountryRefs() {
genesysLowlevelRepository.updateFaoInstituteCountries();
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
@CacheEvict(value = { "statistics", HIBERNATE_CACHENAME }, allEntries = true)
public void delete(String instCode) {
final FaoInstitute institute = getInstitute(instCode);
if (institute != null) {
instituteSettingRepository.deleteFor(institute.getCode());
instituteRepository.delete(institute);
}
}
@Override
@PreAuthorize("isAuthenticated()")
public List<FaoInstitute> listMyInstitutes(Sort sort) {
final AclSid sid = SecurityContextUtil.getCurrentUser();
final List<Long> instituteOIDs = aclService.listObjectIdentityIdsForSid(FaoInstitute.class, sid, BasePermission.WRITE);
final List<Long> partnerOIDs = aclService.listObjectIdentityIdsForSid(Partner.class, sid, BasePermission.WRITE);
final Map<Long, FaoInstitute> institutes = new HashMap<>();
BooleanExpression expression = QFaoInstitute.faoInstitute.owner().id.in(partnerOIDs).or(QFaoInstitute.faoInstitute.id.in(instituteOIDs));
instituteRepository.findAll(expression, sort).spliterator().forEachRemaining(faoInstitute -> institutes.put(faoInstitute.getId(), faoInstitute));
LOG.info("Got {} elements for {}", institutes.size(), sid);
if (institutes.isEmpty()) {
return null;
}
return Lists.newArrayList(institutes.values());
}
@Override
public List<FaoInstitute> autocomplete(String term) {
return instituteRepository.autocomplete("%" + term + "%", PageRequest.of(0, 10));
}
@Override
public List<VocabularyTerm> autocompleteTerm(String ac) {
return autocomplete(ac).stream().map(this::toVocabularyTerm).collect(Collectors.toList());
}
@Override
public Map<String, String> decodeCodes(Set<String> codes) {
/* @formatter:off */
Predicate whereClause = codes != null
? QFaoInstitute.faoInstitute.code.in(codes)
: QFaoInstitute.faoInstitute.accessionCount.gt(0);
List<Tuple> query = jpaQueryFactory.select(QFaoInstitute.faoInstitute.code, QFaoInstitute.faoInstitute.fullName)
.from(QFaoInstitute.faoInstitute)
.where(whereClause)
.fetch();
/* @formatter:on */
return query.stream().collect(Collectors.toMap(tuple -> tuple.get(QFaoInstitute.faoInstitute.code), tuple -> tuple.get(QFaoInstitute.faoInstitute.fullName)));
}
private VocabularyTerm toVocabularyTerm(FaoInstitute institute) {
if (institute == null) {
throw new NotFoundElement("No such wiews term");
}
VocabularyTerm term = new VocabularyTerm();
term.setCode(institute.getCode());
term.setTitle(Optional.ofNullable(institute.getFullName()).orElse(Optional.ofNullable(institute.getAcronym()).orElse(institute.getCode())));
term.setDescription(generateMarkdownForDescription(institute));
return term;
}
/**
* Generate markdown for description of controlled vocabulary term
*
* @param institute institute data
* @return the description for controlled vocabulary term
*/
private String generateMarkdownForDescription(final FaoInstitute institute) {
final String breakLine = " \n";
final String line1 =((Optional.ofNullable (institute.getAcronym()).orElse("")) + " " + (Optional.ofNullable(institute.getFullName()).orElse(""))).trim();
final String line2 = Optional.ofNullable(institute.getEmail()).orElse("");
final String line3 = Optional.ofNullable(institute.getUrl()).orElse("");
String line4 = "";
if (institute.getCountry() != null) {
line4 = institute.getCountry().getCode3();
}
final StringBuilder builder = new StringBuilder();
if (!line1.isEmpty()) {
builder.append(line1).append(breakLine).append(breakLine);
}
if (!line2.isEmpty()) {
builder.append(line2).append(breakLine);
}
if (!line3.isEmpty()) {
builder.append(line3).append(breakLine);
}
if (!line2.isEmpty() || !line3.isEmpty()) {
builder.append(breakLine);
}
if (!line4.isEmpty()) {
builder.append(line4);
}
return builder.toString();
}
@Override
public Page<VocabularyTerm> listTerms(Pageable page) {
Page<FaoInstitute> res = instituteRepository.findAll(page);
return res.map(inst -> toVocabularyTerm(inst));
}
private void copyValues(FaoInstitute target, FaoInstitute source) {
target.setAllowMaterialRequests(source.isAllowMaterialRequests());
target.setUniqueAcceNumbs(source.isUniqueAcceNumbs());
target.setCodeSGSV(source.getCodeSGSV());
if(source.getSettings() != null) {
target.getSettings().putAll(source.getSettings());
}
}
@Override
@Transactional(propagation = Propagation.MANDATORY) // Need to be part of an existing transaction!
@PreAuthorize("hasRole('ADMINISTRATOR')")
@CacheEvict(value = { HIBERNATE_CACHENAME }, allEntries = true)
public FaoInstitute changeInstitute(FaoInstitute currentInstitute, FaoInstitute newInstitute) {
LOG.warn("Migrating from {} to {}", currentInstitute.getCode(), newInstitute.getCode());
newInstitute.setAccessionCount(currentInstitute.getAccessionCount());
newInstitute.setAllowMaterialRequests(currentInstitute.isAllowMaterialRequests());
newInstitute.setUniqueAcceNumbs(currentInstitute.isUniqueAcceNumbs());
newInstitute.setEmail(currentInstitute.getEmail());
// Clear PDCI
newInstitute.setPdciAvg(newInstitute.getPdciAvg());
newInstitute.setPdciMin(newInstitute.getPdciMin());
newInstitute.setPdciMax(newInstitute.getPdciMax());
newInstitute.setPdciHistogram(newInstitute.getPdciHistogram());
var updated = instituteRepository.save(newInstitute);
currentInstitute.setAccessionCount(0); // we move all accessions!
currentInstitute.setAllowMaterialRequests(false);
// currentInstitute.setvCode(newInstitute.getCode()); // Don't point to the new institute!
// Clear PDCI
currentInstitute.setPdciAvg(null);
currentInstitute.setPdciMin(null);
currentInstitute.setPdciMax(null);
currentInstitute.setPdciHistogram(null);
instituteRepository.save(currentInstitute);
return updated;
}
}