PGRFANetworkServiceImpl.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.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.genesys.Accession;
import org.genesys.server.model.impl.FaoInstitute;
import org.genesys.server.model.impl.PGRFANetwork;
import org.genesys.server.model.impl.QFaoInstitute;
import org.genesys.server.persistence.FaoInstituteRepository;
import org.genesys.server.persistence.PGRFANetworkRepository;
import org.genesys.server.service.ArticleService;
import org.genesys.server.service.ArticleTranslationService;
import org.genesys.server.service.ContentService;
import org.genesys.server.service.ElasticsearchService;
import org.genesys.server.service.PGRFANetworkService;
import org.genesys.server.service.filter.AccessionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.Sets;

@Service
@Transactional(readOnly = true)
@Validated
public class PGRFANetworkServiceImpl implements PGRFANetworkService {
	private static final String CACHE_ORGANIZATION_INSTITUTEORGS = "hibernate.org.genesys.server.model.impl.PGRFANetwork.instituteOrganizations";

	public static final Logger LOG = LoggerFactory.getLogger(PGRFANetworkServiceImpl.class);

	private final Set<String> terms = Sets.newHashSet("institute.code", "institute.country.code3", "cropName", "crop.shortName",
			"taxonomy.genus", "taxonomy.species", "taxonomy.genusSpecies", "taxonomy.grinTaxonomySpecies.name", "taxonomy.currentTaxonomySpecies.name",
			"countryOfOrigin.code3", "sampStat", "available", "mlsStatus", "donorCode", "sgsv", "storage", "duplSite", "breederCode", "aegis");

	@Autowired
	private FaoInstituteRepository instituteRepository;

	@Autowired
	private PGRFANetworkRepository networkRepository;

	@Autowired
	private ArticleService articleService;

	@Autowired(required = false)
	private ElasticsearchService elasticsearchService;


	@Override
	public PGRFANetwork getNetwork(String slug) {
		return lazyLoad(networkRepository.findBySlug(slug));
	}

	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@Override
	@Transactional
	@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries = true)
	public PGRFANetwork delete(PGRFANetwork network) {
		network = networkRepository.findById(network.getId()).orElseThrow(() -> new NotFoundElement("Network not found"));
		networkRepository.delete(network);
		network.setId(null);
		return network;
	}

	@Override
	public Page<PGRFANetwork> list(Pageable pageable) {
		return networkRepository.findAll(pageable);
	}

	@Override
	public ArticleTranslationService.TranslatedArticle getBlurb(PGRFANetwork network, Locale locale) {
		return articleService.getArticle(network, ContentService.ENTITY_BLURB_SLUG, locale);
	}

	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@Transactional(readOnly = false)
	public PGRFANetwork update(long id, String newSlug, String title, String accessionFilter) {
		final PGRFANetwork network = new PGRFANetwork();
		network.setId(id);
		network.setSlug(newSlug);
		network.setTitle(title);
		network.setAccessionFilter(accessionFilter);

		return update(network);
	}

	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@Transactional(readOnly = false)
	public PGRFANetwork update(PGRFANetwork network) {
		final PGRFANetwork toUpdate = networkRepository.findBySlug(network.getSlug());
		if (toUpdate == null)
			throw new NotFoundElement("Network not found");

		toUpdate.setSlug(network.getSlug());
		toUpdate.setTitle(network.getTitle());
		toUpdate.setAccessionFilter(network.getAccessionFilter());

		return lazyLoad(networkRepository.save(toUpdate));
	}


	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@Transactional(readOnly = false)
	public PGRFANetwork create(String slug, String title) {
		final PGRFANetwork network = new PGRFANetwork();
		network.setSlug(slug);
		network.setTitle(title);

		return create(network);
	}

	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@Transactional(readOnly = false)
	public PGRFANetwork create(PGRFANetwork network) {
		final PGRFANetwork toCreate = new PGRFANetwork();
		toCreate.setSlug(network.getSlug());
		toCreate.setTitle(network.getTitle());

		return networkRepository.save(toCreate);
	}

	@Override
	public Map<String, ElasticsearchService.TermResult> overview(String slug) throws SearchException {
		var network = networkRepository.findBySlug(slug);
		var accessionFilter = new AccessionFilter();

		if (StringUtils.isNotBlank(network.getAccessionFilter())) {
			try {
				accessionFilter = AccessionFilter.fromJson(network.getAccessionFilter());
			} catch (JsonProcessingException e) {
				LOG.error("Exception in parsing JSON filter: {}", network.getAccessionFilter()); //, e);
			}
		}

		accessionFilter
			.historic(false) // Force active accessions
			.networks(Sets.newHashSet(slug));

		if (elasticsearchService != null) {
			return elasticsearchService.termStatisticsAuto(Accession.class, accessionFilter, 10, terms.toArray(new String[] {}));
		}
		return Map.of();
	}

	@Override
	public List<FaoInstitute> getInstitutes(PGRFANetwork network) {
		if (network == null) {
			throw new InvalidApiUsageException("Network cannot be null");
		}
		return networkRepository.findInstitutesByNetwork(network);
	}

	@Override
	public Page<FaoInstitute> getInstitutes(PGRFANetwork network, Pageable pageable) {
		return instituteRepository.findAll(QFaoInstitute.faoInstitute.in(network.getMembers()), pageable);
	}
	
	@Override
	public Set<FaoInstitute> getMembers(Set<String> networkSlugs) {
		return networkRepository.findInstitutesInNetworks(networkSlugs);
	}

	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries = true)
	public PGRFANetwork setInstitutes(PGRFANetwork org, List<String> instituteList) {
		LOG.info("Setting institutes for network {}", org);

		final PGRFANetwork network = networkRepository.findById(org.getId()).orElseThrow(() -> new NotFoundElement("Network not found"));
		final List<FaoInstitute> toRemove = new ArrayList<FaoInstitute>();

		// Make set of INSTCODEs
		final Set<String> existingMembers = new HashSet<String>();
		for (final FaoInstitute member : network.getMembers()) {
			if (instituteList.contains(member.getCode())) {
				existingMembers.add(member.getCode());
			} else {
				if (LOG.isDebugEnabled()) {
					LOG.debug("Will remove {}", member);
				}
				toRemove.add(member);
			}
		}

		boolean updated = false;

		for (final FaoInstitute institute : toRemove) {
			LOG.info("Removing {}", institute);
			network.getMembers().remove(institute);
			updated = true;
		}

		for (final String wiewsCode : instituteList) {
			if (!existingMembers.contains(wiewsCode)) {
				final FaoInstitute newMemberInstitute = instituteRepository.findByCode(wiewsCode);
				if (newMemberInstitute == null) {
					LOG.warn("No such institute {}", wiewsCode);
				} else {
					LOG.info("Adding {}", newMemberInstitute);
					network.getMembers().add(newMemberInstitute);
					updated = true;
				}
			}
		}

		if (!updated) {
			throw new InvalidApiUsageException("Network cannot be updated");
		}

		LOG.info("Saving {}", network);
		return lazyLoad(networkRepository.save(network));
	}

	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	@CacheEvict(value = CACHE_ORGANIZATION_INSTITUTEORGS, allEntries=true)
	public PGRFANetwork addInstitutes(final PGRFANetwork network, final List<String> instituteList) {
		LOG.info("Adding institutes to network {}", network);

		// Make set of INSTCODEs
		final Set<String> existingMembers = new HashSet<String>();
		for (final FaoInstitute member : network.getMembers()) {
			existingMembers.add(member.getCode());
		}

		boolean updated = false;

		for (final String wiewsCode : instituteList) {
			if (!existingMembers.contains(wiewsCode)) {
				final FaoInstitute newMemberInstitute = instituteRepository.findByCode(wiewsCode);
				if (newMemberInstitute == null) {
					LOG.warn("No such institute {}", wiewsCode);
				} else {
					network.getMembers().add(newMemberInstitute);
					updated = true;
				}
			}
		}

		if (!updated) {
			throw new InvalidApiUsageException("Network cannot be updated");
		}

		return lazyLoad(networkRepository.save(network));
	}

	@Override
	@Cacheable(value = CACHE_ORGANIZATION_INSTITUTEORGS, key = "#faoInstitute.id")
	public List<PGRFANetwork> getNetworksFor(FaoInstitute faoInstitute) {
		if (faoInstitute == null) {
			throw new NullPointerException("faoInstitute");
		}
		return networkRepository.getNetworks(faoInstitute);
	}

	private PGRFANetwork lazyLoad(PGRFANetwork input) {
		if (input == null)
			return null;

		if (input.getMembers() != null)
			input.getMembers().size();

		return input;
	}
}