CitationServiceImpl.java
/*
* Copyright 2024 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 com.querydsl.core.BooleanBuilder;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.genesys.server.api.v2.EntityUuidAndVersion;
import org.genesys.server.component.security.SecurityUtils;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.Partner;
import org.genesys.server.model.PublishState;
import org.genesys.server.model.UserRole;
import org.genesys.server.model.bib.Citation;
import org.genesys.server.model.bib.QCitation;
import org.genesys.server.model.filters.CitationFilter;
import org.genesys.server.model.impl.User;
import org.genesys.server.persistence.CitationRepository;
import org.genesys.server.service.CitationService;
import org.genesys.server.service.UserService;
import org.genesys.util.JPAUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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.Transactional;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import static org.genesys.blocks.security.SecurityContextUtil.hasRole;
@Service
public class CitationServiceImpl extends FilteredCRUDService2Impl<Citation, CitationFilter, CitationRepository> implements CitationService {
@Autowired
private UserService userService;
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Autowired
private SecurityUtils securityUtils;
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or returnObject.state.name().equals('PUBLISHED') or hasPermission(returnObject.owner, 'ADMINISTRATION')")
@Transactional(readOnly = true)
public Citation get(long id) {
return super.get(id);
}
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or returnObject.state.name().equals('PUBLISHED') or hasPermission(returnObject.owner, 'ADMINISTRATION')")
@Transactional(readOnly = true)
public Citation get(UUID uuid) {
return repository.findByUuid(uuid).orElseThrow(() -> new NotFoundElement("No record with uuid=" + uuid));
}
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or returnObject.state.name().equals('PUBLISHED') or hasPermission(returnObject.owner, 'ADMINISTRATION')")
@Transactional(readOnly = true)
public Citation get(EntityUuidAndVersion ref) {
return repository.findByUuidAndVersion(ref.getUuid(), ref.getVersion()).orElseThrow(() -> new NotFoundElement("No such record"));
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or hasPermission(#source.owner, 'ADMINISTRATION')")
public Citation createFast(Citation source) {
source.setState(PublishState.DRAFT);
return super.createFast(source);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or hasPermission(#source.owner, 'ADMINISTRATION')")
public Citation create(Citation source) {
final Citation citation = new Citation();
citation.apply(source);
final Citation saved = repository.save(citation);
return _lazyLoad(saved);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or hasPermission(#updated.owner, 'ADMINISTRATION')")
public Citation update(Citation updated) {
return super.update(updated);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or hasPermission(#target.owner, 'ADMINISTRATION')")
public Citation updateFast(@NotNull @Valid Citation updated, Citation target) {
if (target.getState() == PublishState.PUBLISHED && !securityUtils.hasRole(UserRole.ADMINISTRATOR)) {
throw new InvalidApiUsageException("Cannot update a published citation.");
}
target.apply(updated);
if (updated.getCollections() != null) {
target.setCollections(updated.getCollections());
}
if (updated.getTaxonomyFamilies() != null) {
target.setTaxonomyFamilies(updated.getTaxonomyFamilies());
}
if (updated.getTaxonomySpecies() != null) {
target.setTaxonomySpecies(updated.getTaxonomySpecies());
}
if (updated.getInstituteCodes() != null) {
target.setInstituteCodes(updated.getInstituteCodes());
}
return repository.save(target);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER') or hasPermission(#target.owner, 'ADMINISTRATION')")
public Citation update(Citation updated, Citation target) {
return _lazyLoad(updateFast(updated, target));
}
@Override
@Transactional(readOnly = true)
public Page<Citation> list(CitationFilter filter, Pageable page) throws SearchException {
Page<Citation> res;
if (filter != null && filter.isFulltextQuery()) {
var predicate = QCitation.citation.state.eq(PublishState.PUBLISHED);
res = elasticsearchService.findAll(Citation.class, filter, predicate, page);
} else {
Pageable markdownSortPageRequest = JPAUtils.toMarkdownSort(page, "title");
var filterPublished = new CitationFilter().state(Set.of(PublishState.PUBLISHED));
filter = filter == null ? filterPublished : (CitationFilter) filterPublished.AND(filter);
res = repository.findAll(new BooleanBuilder().and(filter.buildPredicate()), markdownSortPageRequest);
}
return res;
}
@Override
@Transactional(readOnly = true)
public Page<Citation> listMyCitations(CitationFilter filter, Pageable page) throws SearchException {
BooleanBuilder predicate = new BooleanBuilder();
if (!(hasRole("ADMINISTRATOR") || hasRole("CITATIONMANAGER"))) {
final User user = userService.getMe();
if (user == null) {
throw new InvalidApiUsageException("User not found in the system");
}
final HashSet<Long> partnersIds = new HashSet<>(securityUtils.listObjectIdentityIdsForCurrentUser(Partner.class, BasePermission.ADMINISTRATION));
predicate.and(QCitation.citation.owner().id.in(partnersIds));
}
Page<Citation> result;
if (filter != null && filter.isFulltextQuery()) {
result = elasticsearchService.findAll(Citation.class, filter, predicate, page);
} else {
if (filter != null) {
predicate.and(filter.buildPredicate());
}
result = repository.findAll(predicate, page);
}
return result;
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER')")
public Citation remove(Citation entity) {
return super.remove(get(entity));
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER')")
public Citation approveCitation(final Citation citation) {
var loaded = get(citation.getId());
if (!PublishState.REVIEWING.equals(loaded.getState())) {
throw new InvalidApiUsageException("Citation should be submitted before publication");
}
loaded.setState(PublishState.PUBLISHED);
return repository.save(loaded);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER')")
public Citation rejectCitation(final Citation citation) {
var loaded = get(citation.getId());
loaded.setState(PublishState.DRAFT);
return repository.save(loaded);
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CITATIONMANAGER')")
public Citation reviewCitation(final Citation citation) {
var loaded = get(citation.getId());
if (loaded.getState() == PublishState.REVIEWING) {
throw new InvalidApiUsageException("The citation is already under approval");
}
loaded.setState(PublishState.REVIEWING);
return repository.save(loaded);
}
@Override
@Transactional(readOnly = true)
// This can be public
public List<String> getCollections(Partner owner) {
var collections = Expressions.stringPath("collections");
return jpaQueryFactory
.selectDistinct(collections)
.from(QCitation.citation)
.join(QCitation.citation.collections, collections)
.where(QCitation.citation.owner().eq(owner))
.orderBy(collections.asc())
.fetch();
}
@Override
@Transactional(readOnly = true)
// This can be public
public List<String> getCollections(CitationFilter filter) {
var cPredicate = QCitation.citation.state.eq(PublishState.PUBLISHED);
if (filter != null) {
cPredicate = cPredicate.and(filter.buildPredicate());
}
var collections = Expressions.stringPath("collections");
return jpaQueryFactory
.selectDistinct(collections)
.from(QCitation.citation)
.join(QCitation.citation.collections, collections)
.where(cPredicate)
.orderBy(collections.asc())
.fetch();
}
}