TaxonomySpeciesCRUDServiceImpl.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.gringlobal.service.impl;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.QTaxonomySpecies;
import org.gringlobal.model.TaxonomyGenus;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.persistence.TaxonomySpeciesRepository;
import org.gringlobal.service.TaxonomyGenusCRUDService;
import org.gringlobal.service.TaxonomySpeciesCRUDService;
import org.gringlobal.service.filter.TaxonomySpeciesFilter;
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.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQuery;

/**
 * The Class TaxonomySpeciesCRUDService.
 */
@Service
@Slf4j
public class TaxonomySpeciesCRUDServiceImpl extends FilteredCRUDServiceImpl<TaxonomySpecies, TaxonomySpeciesFilter, TaxonomySpeciesRepository> implements
		TaxonomySpeciesCRUDService {

	final static Pattern PATTERN_SUBSP = Pattern.compile("subsp\\.\\s*(\\w+)");
	final static Pattern PATTERN_VAR = Pattern.compile("var\\.\\s*(\\w+)");
	final static Pattern PATTERN_SUBV = Pattern.compile("subvar\\.\\s*(\\w+)");
	final static Pattern PATTERN_FORM = Pattern.compile("f\\.\\s*(\\w+)");

	private static final String[] BOOST_FIELDS = { "speciesName", "name", "taxonomyGenus.genusName" };
	
	@Autowired
	private TaxonomyGenusCRUDService taxonomyGenusService;
	
	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Taxonomy', 'CREATE')")
	public TaxonomySpecies create(TaxonomySpecies source) {
		TaxonomySpecies record = new TaxonomySpecies();
		record.apply(source);
		record.setTaxonomyGenus(taxonomyGenusService.get(record.getTaxonomyGenus().getId()));
		record = repository.save(record);
		if (record.getCurrentTaxonomySpecies() == null) {
			record.setCurrentTaxonomySpecies(record);
			record = repository.save(record);
		}
		return _lazyLoad(record);
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Taxonomy', 'ADMINISTRATION')")
	public TaxonomySpecies update(TaxonomySpecies updated, TaxonomySpecies target) {
		if (target.getGrinId() != null) {
			throw new InvalidApiUsageException("TaxonomySpecies records sourced from GRIN Taxonomy are unmodifiable");
		}
		target.apply(updated);
		target.setTaxonomyGenus(taxonomyGenusService.get(target.getTaxonomyGenus().getId()));
		return _lazyLoad(repository.save(target));
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Taxonomy', 'DELETE')")
	public TaxonomySpecies remove(TaxonomySpecies entity) {
		return super.remove(entity);
	}

	@Override
	public Page<TaxonomySpecies> list(TaxonomySpeciesFilter filter, Pageable page) throws SearchException {
		return super.list(TaxonomySpecies.class, filter, page, BOOST_FIELDS);
	}

	@Override
	protected JPAQuery<TaxonomySpecies> entityListQuery() {
		return jpaQueryFactory.selectFrom(QTaxonomySpecies.taxonomySpecies)
			//
			.join(QTaxonomySpecies.taxonomySpecies.taxonomyGenus()).fetchJoin()
			// owner
			.join(QTaxonomySpecies.taxonomySpecies.ownedBy()).fetchJoin();
	}

	@Override
	protected NumberPath<Long> entityIdPredicate() {
		return QTaxonomySpecies.taxonomySpecies.id;
	}

	@Override
	@Transactional(readOnly = false)
	public TaxonomySpecies fromMCPD(String genus, String specificEpithet, String spAuthor, String subTaxa, String subtAuthor) {
//		LOG.warn("Ensuring taxonomy {} {} {} {} {}", genus, specificEpithet, spAuthor, subTaxa, subtAuthor);
		
		if (StringUtils.startsWithIgnoreCase(genus, "X")) {
			// Trim starting hybid code
			genus = genus.substring(1).strip();
		}
		
		if ("sp.".equalsIgnoreCase(specificEpithet)) {
			// GRIN Taxonomy style
			specificEpithet =  "spp.";
		}
		
		String subsp1 = null;
		String var1 = null;
		String subvar1 = null;
		String forma1 = null;
		
		if (StringUtils.isNotBlank(subTaxa)) {
			Matcher m = PATTERN_SUBSP.matcher(subTaxa);
			if (m.find()) {
				subsp1 = m.group(1);
			}
			m = PATTERN_VAR.matcher(subTaxa);
			if (m.find()) {
				var1 = m.group(1);
			}
			m = PATTERN_SUBV.matcher(subTaxa);
			if (m.find()) {
				subvar1 = m.group(1);
			}
			m = PATTERN_FORM.matcher(subTaxa);
			if (m.find()) {
				forma1 = m.group(1);
			}
		}

		final String subsp = StringUtils.defaultIfBlank(subsp1, null);
		final String varValue = StringUtils.defaultIfBlank(var1, null);
		final String subvar = StringUtils.defaultIfBlank(subvar1, null);
		final String forma = StringUtils.defaultIfBlank(forma1, null);

		List<TaxonomySpecies> matches = jpaQueryFactory.selectFrom(QTaxonomySpecies.taxonomySpecies).where(
			// species name
			QTaxonomySpecies.taxonomySpecies.speciesName.eq(specificEpithet)
				// genus
				.and(QTaxonomySpecies.taxonomySpecies.taxonomyGenus().genusName.eq(genus))
		// emm
		).fetch();

		log.debug("Got {} matches for {} {}", matches.size(), genus, specificEpithet);
		TaxonomySpecies best = null;
		TaxonomySpecies topLevel = null;
		if (matches.size() > 0) {
			for (TaxonomySpecies match : matches) {
				log.debug("Match {} {} {} {} {} ?== {} <== subsp.{} var.{} subvar.{} f.{}", genus, specificEpithet, spAuthor, subTaxa, subtAuthor, match.getName(), match
					.getSubspeciesName(), match.getVarietyName(), match.getSubvarietyName(), match.getFormaName());
				if (subsp != null && subsp.equalsIgnoreCase(match.getSubspeciesName())) {
					best = match;
				}
				if (varValue != null && varValue.equalsIgnoreCase(match.getVarietyName())) {
					best = match;
				}
				if (subvar != null && varValue.equalsIgnoreCase(match.getSubvarietyName())) {
					best = match;
				}
				if (forma != null && forma.equalsIgnoreCase(match.getFormaName())) {
					best = match;
				}
				if (best == null && match.getSubspeciesName() == null && match.getVarietyName() == null && match.getSubvarietyName() == null && match.getFormaName() == null) {
					// Toplevel name if nothing else
					best = topLevel = match;
				}
			}
			if (best != null) {
				if (topLevel == best && (subsp != null || varValue != null || subvar != null || forma != null)) {
					// Incoming species has details, make new record.
				} else {
					log.info("Best match {} {} {} {} {} ?== {} <== subsp.{} var.{} subvar.{} f.{}", genus, specificEpithet, spAuthor, subTaxa, subtAuthor, best.getName(), best.getSubspeciesName(), best.getVarietyName(), best.getSubvarietyName(), best.getFormaName());
					return best;
				}
			}
		}

		TaxonomyGenus taxonomyGenus = taxonomyGenusService.getGenus(genus, null);
		if (taxonomyGenus == null) {
			return null;
		}

		// Figure out authority name on lowest level
		String subspAuthority = null;
		String varAuthority = null;
		String subvarAuthority = null;
		String formaAuthority = null;

		if (StringUtils.isNotBlank(subtAuthor)) {
			if (forma != null) {
				formaAuthority = subtAuthor;
			} else if (subvar != null) {
				subvarAuthority = subtAuthor;
			} else if (varValue != null) {
				varAuthority = subtAuthor;
			} else if (subsp != null) {
				subspAuthority = subtAuthor;
			}
		}
		
		TaxonomySpecies taxonomySpecies = new TaxonomySpecies();
		taxonomySpecies.setTaxonomyGenus(taxonomyGenus);
		taxonomySpecies.setSpeciesName(specificEpithet);
		taxonomySpecies.setSpeciesAuthority(spAuthor);
		taxonomySpecies.setSubspeciesName(subsp);
		taxonomySpecies.setVarietyAuthority(subspAuthority);
		taxonomySpecies.setVarietyName(varValue);
		taxonomySpecies.setVarietyAuthority(varAuthority);
		taxonomySpecies.setSubvarietyName(subvar);
		taxonomySpecies.setFormaAuthority(subvarAuthority);
		taxonomySpecies.setFormaName(forma);
		taxonomySpecies.setFormaAuthority(formaAuthority);
		taxonomySpecies.setFormaRankType(forma == null ? null : "f.");
		taxonomySpecies.setNote("From MCPD " + genus + " " + specificEpithet + " " + spAuthor + " " + subTaxa + " " + subtAuthor);
		log.info("New record {} {} {} {} {}", genus, specificEpithet, spAuthor, subTaxa, subtAuthor);
		return repository.save(taxonomySpecies);
	}
}