PartnerServiceImpl.java

/*
 * 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.
 * 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 static org.genesys.server.model.QPartner.partner;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys.server.component.security.SecurityUtils;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.Partner;
import org.genesys.server.model.UserRole;
import org.genesys.server.model.dataset.Dataset;
import org.genesys.server.model.filters.DatasetFilter;
import org.genesys.server.model.filters.DescriptorListFilter;
import org.genesys.server.model.filters.PartnerFilter;
import org.genesys.server.model.impl.FaoInstitute;
import org.genesys.server.model.impl.QFaoInstitute;
import org.genesys.server.model.impl.Subset;
import org.genesys.server.model.traits.DescriptorList;
import org.genesys.server.persistence.FaoInstituteRepository;
import org.genesys.server.persistence.PartnerRepository;
import org.genesys.server.service.DatasetService;
import org.genesys.server.service.DescriptorListService;
import org.genesys.server.service.PartnerService;
import org.genesys.server.service.SubsetService;
import org.genesys.server.service.filter.InstituteFilter;
import org.genesys.server.service.filter.SubsetFilter;
import org.genesys.util.JPAUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.data.domain.Sort;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
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 org.springframework.validation.annotation.Validated;

import com.querydsl.core.types.Predicate;


/**
 * {@inheritDoc}.
 */
@Slf4j
@Service
@Transactional(readOnly = true)
@Validated
public class PartnerServiceImpl extends FilteredCRUDService2Impl<Partner, PartnerFilter, PartnerRepository> implements PartnerService, InitializingBean {

	@Autowired
	private SubsetService subsetService;
	@Autowired
	private DatasetService datasetService;
	@Autowired
	private DescriptorListService descriptorListService;

	@Autowired
	private FaoInstituteRepository instituteRepository;

	@Autowired
	private SecurityUtils securityUtils;

	@Autowired
	private CustomAclService aclService;

	@Value("${partner.primary.uuid}")
	private UUID primaryPartnerUuid;

	@Override
	@Transactional
	public void afterPropertiesSet() throws Exception {
		try {
			getPrimaryPartner();
		} catch (NotFoundElement e) {
			var primaryPartner = new Partner();
			primaryPartner.setUuid(primaryPartnerUuid);
			primaryPartner.setName("Genesys");
			primaryPartner.setShortName("genesys");
			primaryPartner = create(primaryPartner);
		}
	}

	@Override
	public Partner get(UUID uuid) {
		return repository.findByUuid(uuid).orElseThrow(() -> new NotFoundElement("No record with uuid=" + uuid));
	}

	@Override
	public Partner load(UUID uuid) {
		return _lazyLoad(get(uuid));
	}

	@Override
	public Partner getPrimaryPartner() {
		return get(primaryPartnerUuid);
	}

	@Override
	public List<Partner> autocompletePartners(final String term, final int limit) {
		if (!StringUtils.isBlank(term) && term.length() >= 1) {
			log.debug("Autocomplete partners for={}", term);
			Predicate predicate = partner.name.startsWithIgnoreCase(term).or(partner.shortName.startsWithIgnoreCase(term).or(partner.name.containsIgnoreCase(term)));
			return repository.findAll(predicate, PageRequest.of(0, Math.min(100, limit), Sort.by("name"))).getContent();
		} else {
			return Collections.emptyList();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional
	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	public Partner create(final Partner input) {
		log.debug("Creating partner: {} - {}", input.getShortName(), input.getName());
		final Partner partner = new Partner();
		partner.setUuid(input.getUuid());
		partner.apply(input);
		final Partner saved = repository.save(partner);

		// Ensure ACL SID entry for the partner shortName
		aclService.ensureAuthoritySid(saved.getAuthorityName());

		return _lazyLoad(saved);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#target, 'write')")
	public Partner update(Partner updated, Partner target) {
		return _lazyLoad(updateFast(updated, target));
	}

	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#target, 'write')")
	public Partner updateFast(Partner updated, Partner target) {
		target.apply(updated);
		return repository.save(target);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Page<Partner> listPartnersForCurrentUser(final PartnerFilter filter, final Pageable page) {
		if (securityUtils.hasRole(UserRole.ADMINISTRATOR)) {
			Pageable markdownSortPageRequest = JPAUtils.toMarkdownSort(page, "name");
			Page<Partner> res = repository.findAll(filter.buildPredicate(), markdownSortPageRequest);
			return new PageImpl<>(res.getContent(), page, res.getTotalElements());
		} else {
			final HashSet<Long> ids = new HashSet<>(securityUtils.listObjectIdentityIdsForCurrentUser(Partner.class, BasePermission.WRITE));
			Pageable markdownSortPageRequest = JPAUtils.toMarkdownSort(page, "name");
			Page<Partner> res = repository.findAll(partner.id.in(ids).and(filter.buildPredicate()), markdownSortPageRequest);
			return new PageImpl<>(res.getContent(), page, res.getTotalElements());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Partner> listAllPartnersForCurrentUser() {
		if (securityUtils.hasRole(UserRole.ADMINISTRATOR)) {
			return repository.findAll();
		} else {
			final HashSet<Long> ids = new HashSet<>(securityUtils.listObjectIdentityIdsForCurrentUser(Partner.class, BasePermission.WRITE));
			return Lists.newArrayList(repository.findAll(partner.id.in(ids)));
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Page<Partner> list(final PartnerFilter partnerFilter, final Pageable page) throws SearchException {
		Pageable markdownSortPageRequest = JPAUtils.toMarkdownSort(page, "name");
		Page<Partner> res = super.list(partnerFilter, markdownSortPageRequest);
		return new PageImpl<>(res.getContent(), page, res.getTotalElements());
	}

	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	public Partner addInstitutes(Partner partner, Set<String> instituteCodes) {
		final Partner savedPartner = get(partner.getUuid());

		List<FaoInstitute> institutes = instituteRepository.findAllByCodes(instituteCodes);
		institutes.forEach((inst) -> inst.setOwner(savedPartner));
		institutes.forEach((institute -> aclService.createOrUpdatePermissions(institute)));
		savedPartner.getInstitutes().addAll(institutes);

		return _lazyLoad(repository.save(savedPartner));
	}

	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	public Partner removeInstitutes(Partner partner, Set<String> instituteCodes) {
		partner = get(partner.getUuid());

		List<FaoInstitute> institutesToRemove = instituteRepository.findAllByCodes(instituteCodes);
		institutesToRemove.forEach((institute -> institute.setOwner(null)));
		institutesToRemove.forEach((institute -> aclService.createOrUpdatePermissions(institute)));
		instituteRepository.saveAll(institutesToRemove);

		partner.getInstitutes().removeAll(institutesToRemove);
		return _lazyLoad(repository.save(partner));
	}

	@Override
	public PartnerDetails loadPartnerDetails(UUID uuid) {
		Partner partner = load(uuid);

		PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "lastModifiedDate"));

		SubsetFilter subsetFilter = new SubsetFilter();
		subsetFilter.owner().uuid(Set.of(partner.getUuid()));
		List<Subset> recentSubsets = null;
		try {
			recentSubsets = subsetService.list(subsetFilter, pageRequest).getContent();
		} catch (SearchException e) {
			log.error("Error occurred during search", e);
		}

		DatasetFilter datasetFilter = new DatasetFilter();
		datasetFilter.owner().uuid(Set.of(partner.getUuid()));
		List<Dataset> recentDatasets = null;
		try {
			recentDatasets = datasetService.list(datasetFilter, pageRequest).getContent();
		} catch (SearchException e) {
			log.error("Error occurred during search", e);
		}

		DescriptorListFilter descriptorListFilter = new DescriptorListFilter();
		descriptorListFilter.owner().uuid(Set.of(partner.getUuid()));
		List<DescriptorList> recentDescriptorLists = null;
		try {
			recentDescriptorLists = descriptorListService.list(descriptorListFilter, pageRequest).getContent();
		} catch (SearchException e) {
			log.error("Error occurred during search", e);
		}

		return PartnerDetails.from(partner, recentSubsets, recentDatasets, recentDescriptorLists);
	}

	@Override
	public Page<FaoInstitute> loadPartnerInstitutes(UUID uuid, InstituteFilter filter, Pageable page) {
		return instituteRepository.findAll(QFaoInstitute.faoInstitute.owner().uuid.eq(uuid).and(filter.buildPredicate()), page);
	}

	@Override
	public Set<FaoInstitute> loadInstitutes(Set<UUID> partnersUUIDs) {
		return repository.findInstitutesInPartners(partnersUUIDs);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Partner loadPartner(final UUID uuid, final int version) {
		return _lazyLoad(repository.findByUuidAndVersion(uuid, version).orElseThrow(() -> new ObjectOptimisticLockingFailureException(Partner.class, uuid)));
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional
	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	public Partner remove(final Partner partner) {
		Partner removedPartner = super.remove(partner);

		aclService.removeAuthoritySid(partner.getAuthorityName());

		return removedPartner;
	}

	@Override
	public long countPartners(PartnerFilter filter) {
		return repository.count(filter.buildPredicate());
	}

}