AccessionFilter.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.filter;

import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.filters.TemporalFilter;
import org.genesys.blocks.model.filters.NumberFilter;
import org.genesys.blocks.model.filters.StringFilter;
import org.genesys.blocks.util.CurrentApplicationContext;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.model.Accession;
import org.gringlobal.model.QAccession;
import org.gringlobal.model.QAccessionAction;
import org.gringlobal.model.QAccessionSource;

import com.querydsl.core.types.Predicate;
import org.gringlobal.model.QCropTraitObservation;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QSourceDescObservation;
import org.gringlobal.persistence.AccessionInvGroupMapRepository;

/**
 * Filters for {@link Accession}
 */
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true)
public class AccessionFilter extends CooperatorOwnedModelFilter<AccessionFilter, Accession> implements IFullTextFilter {

	private static final long serialVersionUID = -7684502726900427124L;

	/** Any text. */
	public String _text;

	/** The doi. */
	public Set<String> doi;

	/** The acce preferred name */
	public StringFilter preferredName;

	/** The acce numb. */
	public Set<String> accessionNumber;

	/** The acce numb part1. */
	public StringFilter accessionNumberPart1;

	/** The acce numb part2. */
	public NumberFilter<Long> accessionNumberPart2;

	/** The acce numb part3. */
	public StringFilter accessionNumberPart3;

	/** The backup location1 site. */
	public SiteFilter backupLocation1Site;

	/** The backup location2 site. */
	public SiteFilter backupLocation2Site;

	/** The improvement status code. */
	public Set<String> improvementStatusCode;

	/** The MLS status. */
	public Set<String> mlsStatus;

	/** The initial received date. */
	public TemporalFilter<Date> initialReceivedDate;

	/** The initial received date code. */
	public Set<String> initialReceivedDateCode;

	/** The initial received form code. */
	public Set<String> initialReceivedFormCode;

	/** is backed up */
	public Boolean backedUp;

	/** is core */
	public Boolean core;

	/** is web visible */
	public Boolean webVisible;

	/** The life form code. */
	public Set<String> lifeFormCode;

	/** The note. */
	public StringFilter note;

	/** The reproductive uniformity code. */
	public Set<String> reproductiveUniformityCode;

	/** The status code. */
	public Set<String> statusCode;

	/** The taxonomy species filter. */
	public TaxonomySpeciesFilter taxonomySpecies;

	/** Accession sources */
	public AccessionSourceFilter sources;

	/** Site */
	public SiteFilter site;

	/** The Accession inventory group ids. */
	public Set<Long> accessionInvGroup;

	/** The Accession actions */
	public AccessionActionFilter accessionActions;

	/** The Accession inventories */
	public InventoryFilter inventories;

	/** The AccessionIprs */
	public AccessionIprFilter accessionIprs;

	/** The curation type code. */
	public Set<String> curationTypeCode;

	/** The source observations values. */
	public List<SourceDescObservationFilter.SourceDescValueFilter> sourceObservations;

	/** The crop traits value */
	public List<CropTraitObservationFilter.CropTraitValueFilter> cropTraits;

	/** Accession names */
	public StringFilter plantName;

	/** The exploration */
	public ExplorationFilter exploration;

	/** The collection */
	public Set<String> collectionCode;

	/** The attachment */
	public AccessionInvAttachFilter accessionInvAttach;

	/**
	 * Builds the query.
	 *
	 * @return the predicate
	 */
	@Override
	public List<Predicate> collectPredicates() {
		return collectPredicates(QAccession.accession);
	}

	/**
	 * Builds the query.
	 *
	 * @param accession the accession
	 * @return the predicate
	 */
	public List<Predicate> collectPredicates(QAccession accession) {
		final List<Predicate> predicates = super.collectPredicates(accession, accession._super);

		if (CollectionUtils.isNotEmpty(doi)) {
			predicates.add(accession.doi.in(doi));
		}
		if (CollectionUtils.isNotEmpty(accessionNumber)) {
			predicates.add(accession.accessionNumber.in(accessionNumber));
		}
		if (preferredName != null) {
			predicates.add(preferredName.buildQuery(accession.preferredName));
		}
		if (accessionNumberPart1 != null) {
			predicates.add(accessionNumberPart1.buildQuery(accession.accessionNumberPart1));
		}
		if (accessionNumberPart2 != null) {
			predicates.add(accessionNumberPart2.buildQuery(accession.accessionNumberPart2));
		}
		if (accessionNumberPart3 != null) {
			predicates.add(accessionNumberPart3.buildQuery(accession.accessionNumberPart3));
		}
		if (site != null) {
			predicates.addAll(site.collectPredicates(accession.site()));
		}
		if (backupLocation1Site != null) {
			predicates.addAll(backupLocation1Site.collectPredicates(accession.backupLocation1Site()));
		}
		if (backupLocation2Site != null) {
			predicates.addAll(backupLocation2Site.collectPredicates(accession.backupLocation2Site()));
		}
		if (CollectionUtils.isNotEmpty(improvementStatusCode)) {
			predicates.add(accession.improvementStatusCode.in(improvementStatusCode));
		}
		if (CollectionUtils.isNotEmpty(mlsStatus)) {
			predicates.add(accession.mlsStatus.in(mlsStatus));
		}
		if (initialReceivedDate != null) {
			predicates.add(initialReceivedDate.buildQuery(accession.initialReceivedDate));
		}
		if (CollectionUtils.isNotEmpty(initialReceivedDateCode)) {
			predicates.add(accession.initialReceivedDateCode.in(initialReceivedDateCode));
		}
		if (CollectionUtils.isNotEmpty(initialReceivedFormCode)) {
			predicates.add(accession.initialReceivedFormCode.in(initialReceivedFormCode));
		}
		if (backedUp != null) {
			predicates.add(accession.isBackedUp.eq(convertToString(backedUp)));
		}
		if (core != null) {
			predicates.add(accession.isCore.eq(convertToString(core)));
		}
		if (webVisible != null) {
			predicates.add(accession.isWebVisible.eq(convertToString(webVisible)));
		}
		if (CollectionUtils.isNotEmpty(lifeFormCode)) {
			predicates.add(accession.lifeFormCode.in(lifeFormCode));
		}
		if (note != null) {
			predicates.add(note.buildQuery(accession.note));
		}
		if (CollectionUtils.isNotEmpty(reproductiveUniformityCode)) {
			predicates.add(accession.reproductiveUniformityCode.in(reproductiveUniformityCode));
		}
		if (CollectionUtils.isNotEmpty(statusCode)) {
			predicates.add(accession.statusCode.in(statusCode));
		}
		if (CollectionUtils.isNotEmpty(curationTypeCode)) {
			predicates.add(accession.curationTypeCode.in(curationTypeCode));
		}
		if (taxonomySpecies != null) {
			predicates.addAll(taxonomySpecies.collectPredicates(accession.taxonomySpecies()));
		}
		if (sources != null) {
			var qAccessionSource = new QAccessionSource(accession.accessionSources.getMetadata().getName()); // alias MUST match accession.accessionSources path
			predicates.add(nestedFilter(accession.accessionSources, qAccessionSource, qAccessionSource.accession().eq(accession), sources.collectPredicates(qAccessionSource)));
		}
		if (accessionInvGroup != null) {
			predicates.add(accession.inventories.any().id.in(getInventoryMembersOfGroups(accessionInvGroup)));
		}
		if (accessionActions != null) {
			var qAccessionAction = new QAccessionAction(accession.accessionActions.getMetadata().getName());
			predicates.add(nestedFilter(accession.accessionActions, qAccessionAction, qAccessionAction.accession().eq(accession), accessionActions.collectPredicates(qAccessionAction)));
		}
		if (inventories != null) {
			predicates.addAll(inventories.collectPredicates(accession.inventories.any()));
		}
		if (accessionIprs != null) {
			predicates.addAll(accessionIprs.collectPredicates(accession.accessionIprs.any()));
		}
		if (CollectionUtils.isNotEmpty(sourceObservations)) {
			if (StringUtils.isNotBlank(_text)) throw new InvalidApiUsageException("_text search is not available when filtering sourceObservations");
			predicates.add(accession.id.in(processSourceDescObservations(sourceObservations)));
		}
		if (CollectionUtils.isNotEmpty(cropTraits)) {
			if (StringUtils.isNotBlank(_text)) throw new InvalidApiUsageException("_text search is not available when filtering cropTrats");
			predicates.add(accession.id.in(processCropTraitObservations(cropTraits)));
		}
		if (plantName != null) {
			predicates.add(plantName.buildQuery(accession.inventories.any().names.any().plantName));
		}
		if(exploration != null) {
			predicates.addAll(exploration.collectPredicates(accession.exploration()));
		}
		if (CollectionUtils.isNotEmpty(collectionCode)) {
			predicates.add(accession.collectionCode.in(collectionCode));
		}
		if (accessionInvAttach != null) {
			predicates.addAll(accessionInvAttach.collectPredicates(accession.inventories.any().attachments.any()));
		}

		return predicates;
	}

	private JPAQuery<Long> processSourceDescObservations(List<SourceDescObservationFilter.SourceDescValueFilter> sourceObservations) {
		var jpaQueryFactory = CurrentApplicationContext.getContext().getBean(JPAQueryFactory.class);

		JPAQuery<Long> query = null;
		int queryNum = 0;
		for (var sourceFilter : sourceObservations) {
			var qPath = new QSourceDescObservation("sourceObs" + queryNum++);
			var predicate = sourceFilter.buildPredicate(qPath);
			var sourceDescQuery = jpaQueryFactory.selectDistinct(qPath.accessionSource().accession().id).from(qPath).where(predicate);
			if (query != null) {
				// Intersection of the two sets of accession.ids
				sourceDescQuery.where(qPath.accessionSource().accession().id.in(query));
			}
			query = sourceDescQuery; 
		}

		return query;
	}

	private JPAQuery<Long> processCropTraitObservations(List<CropTraitObservationFilter.CropTraitValueFilter> cropTraits) {
		var jpaQueryFactory = CurrentApplicationContext.getContext().getBean(JPAQueryFactory.class);
		
		JPAQuery<Long> query = null;
		int queryNum = 0;
		for (var cropTraitFilter : cropTraits) {
			var qPath = new QCropTraitObservation("cropTraitObs" + queryNum++);
			var predicate = cropTraitFilter.buildPredicate(qPath);
			var traitQuery = jpaQueryFactory.selectDistinct(qPath.inventory().id).from(qPath).where(predicate);
			if (query != null) {
				// Intersection of the two sets of inventory.ids
				traitQuery.where(qPath.inventory().id.in(query));
			}
			query = traitQuery; 
		}

		var qInventory = QInventory.inventory;
		var accessionQuery = jpaQueryFactory.selectDistinct(qInventory.accession().id).from(qInventory).where(qInventory.id.in(query));

		return accessionQuery;
	}

	private List<Long> getInventoryMembersOfGroups(Collection<Long> ids) {
		var repository = CurrentApplicationContext.getContext().getBean(AccessionInvGroupMapRepository.class);
		return repository.findAllByAccessionInvGroup_IdIn(ids).stream()
			.map(groupMap -> groupMap.getInventory().getId())
			.collect(Collectors.toList());
	}

	/**
	 * Gets the text.
	 *
	 * @return the text
	 */
	@Override
	public String get_text() {
		return _text;
	}

	/**
	 * Id.
	 *
	 * @return the set of Ids
	 */
	public synchronized Set<Long> id() {
		if (id == null) {
			id = new HashSet<>();
		}
		return id;
	}

	/**
	 * DOI.
	 *
	 * @return the set of DOIs
	 */
	public synchronized Set<String> doi() {
		return this.doi == null ? this.doi = new HashSet<>() : this.doi;
	}

	/**
	 * The accessionNumbers.
	 *
	 * @return the set of accessionNumbers
	 */
	public synchronized Set<String> accessionNumbers() {
		return this.accessionNumber == null ? this.accessionNumber = new HashSet<>() : this.accessionNumber;
	}

	/**
	 * Taxonomy species filter.
	 *
	 * @return the taxonomy species filter
	 */
	public synchronized TaxonomySpeciesFilter taxonomySpecies() {
		return this.taxonomySpecies == null ? this.taxonomySpecies = new TaxonomySpeciesFilter() : this.taxonomySpecies;
	}

}