InventoryFilter.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.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.Inventory;
import org.gringlobal.model.QCropTraitObservation;
import org.gringlobal.model.QInventory;

import com.querydsl.core.types.Predicate;
import org.gringlobal.model.QInventoryAction;

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

	private static final long serialVersionUID = -6168038817307724417L;

	/** Any text. */
	public String _text;
	
	/** Include system inventories */
	public Boolean includeSystem = false;

	/** The accession. */
	public AccessionFilter accession;

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

	/** Site */
	public SiteFilter site;

	/** The availability end date. */
	public TemporalFilter<Date> availabilityEndDate;

	/** The availability start date. */
	public TemporalFilter<Date> availabilityStartDate;

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

	/** The availability status note. */
	public StringFilter availabilityStatusNote;

	/** The backup inventory. */
	public Long backupInventory;

	/** The distribution critical quantity. */
	public NumberFilter<Double> distributionCriticalQuantity;

	/** The distribution default form code. */
	public Set<String> distributionDefaultFormCode;

	/** The distribution unit code. */
	public Set<String> distributionUnitCode;

	/** The form type code. */
	public Set<String> formTypeCode;

	/** The distribution default quantity. */
	public NumberFilter<Double> distributionDefaultQuantity;

	/** The hundred seed weight. */
	public NumberFilter<Double> hundredSeedWeight;

	/** The inventory number. */
	public Set<String> inventoryNumber;

	/** The inventory number part1. */
	public StringFilter inventoryNumberPart1;

	/** The inventory number part2. */
	public NumberFilter<Long> inventoryNumberPart2;

	/** The inventory number part3. */
	public StringFilter inventoryNumberPart3;

	/** Is auto deducted */
	public Boolean autoDeducted;

	/** Is available */
	public Boolean available;

	/** Is distributable */
	public Boolean distributable;

	/** The latitude. */
	public NumberFilter<Double> latitude;

	/** The longitude. */
	public NumberFilter<Double> longitude;

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

	/** The parent inventory. */
	public Long parentInventory;

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

	/** The plant sex code. */
	public Set<String> plantSexCode;

	/** The pollination method code. */
	public Set<String> pollinationMethodCode;

	/** The pollination vector code. */
	public Set<String> pollinationVectorCode;

	/** The preservation method. */
	public MethodFilter preservationMethod;

	/** The regeneration method. */
	public MethodFilter regenerationMethod;

	/** The inventory maintenance policy. */
	public InventoryMaintenancePolicyFilter inventoryMaintenancePolicy;

	/** The propagation date. */
	public TemporalFilter<Date> propagationDate;

	/** The propagation date code. */
	public Set<String> propagationDateCode;

	/** The quantity on hand. */
	public NumberFilter<Double> quantityOnHand;

	/** The quantity on hand unit code. */
	public Set<String> quantityOnHandUnitCode;

	/** The regeneration critical quantity. */
	public NumberFilter<Double> regenerationCriticalQuantity;

	/** The rootstock. */
	public StringFilter rootstock;

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

	/** The storage location part1. */
	public StringFilter storageLocationPart1;

	/** The storage location part2. */
	public StringFilter storageLocationPart2;

	/** The storage location part3. */
	public StringFilter storageLocationPart3;

	/** The storage location part4. */
	public StringFilter storageLocationPart4;

	/** The web availability note. */
	public StringFilter webAvailabilityNote;

	/** The accession inventory group id. */
	public Set<Long> accessionInvGroup;

	/** The inventory action filter */
	public InventoryActionFilter actions;

	/** The inventory action filter */
	public AccessionInvNameFilter names;

	/** The inventory extra filter */
	public InventoryExtraFilter extra;

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

	/** The production location geography */
	public GeographyFilter productionLocationGeography;

	/** Inventory names */
	public StringFilter plantName;

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

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

		if (includeSystem != null && ! includeSystem) {
			predicates.add(inventory.formTypeCode.ne(Inventory.SYSTEM_INVENTORY_FTC));
		}
		if (accession != null) {
			predicates.addAll(accession.collectPredicates(inventory.accession()));
		}
		if (site != null) {
			predicates.addAll(site.collectPredicates(inventory.site()));
		}
		if (availabilityEndDate != null) {
			predicates.add(availabilityEndDate.buildQuery(inventory.availabilityEndDate));
		}
		if (availabilityStartDate != null) {
			predicates.add(availabilityStartDate.buildQuery(inventory.availabilityStartDate));
		}
		if (CollectionUtils.isNotEmpty(availabilityStatusCode)) {
			predicates.add(inventory.availabilityStatusCode.in(availabilityStatusCode));
		}
		if (availabilityStatusNote != null) {
			predicates.add(availabilityStatusNote.buildQuery(inventory.availabilityStatusNote));
		}
		if (preferredName != null) {
			predicates.add(preferredName.buildQuery(inventory.preferredName));
		}
		if (backupInventory != null) {
			predicates.add(inventory.backupInventory().id.eq(backupInventory));
		}
		if (distributionCriticalQuantity != null) {
			predicates.add(distributionCriticalQuantity.buildQuery(inventory.distributionCriticalQuantity));
		}
		if (CollectionUtils.isNotEmpty(distributionDefaultFormCode)) {
			predicates.add(inventory.distributionDefaultFormCode.in(distributionDefaultFormCode));
		}
		if (CollectionUtils.isNotEmpty(distributionUnitCode)) {
			predicates.add(inventory.distributionUnitCode.in(distributionUnitCode));
		}
		if (CollectionUtils.isNotEmpty(formTypeCode)) {
			predicates.add(inventory.formTypeCode.in(formTypeCode));
		}
		if (distributionDefaultQuantity != null) {
			predicates.add(distributionDefaultQuantity.buildQuery(inventory.distributionDefaultQuantity));
		}
		if (hundredSeedWeight != null) {
			predicates.add(hundredSeedWeight.buildQuery(inventory.hundredSeedWeight));
		}
		if (CollectionUtils.isNotEmpty(inventoryNumber)) {
			predicates.add(inventory.inventoryNumber.in(inventoryNumber));
		}
		if (inventoryNumberPart1 != null) {
			predicates.add(inventoryNumberPart1.buildQuery(inventory.inventoryNumberPart1));
		}
		if (inventoryNumberPart2 != null) {
			predicates.add(inventoryNumberPart2.buildQuery(inventory.inventoryNumberPart2));
		}
		if (inventoryNumberPart3 != null) {
			predicates.add(inventoryNumberPart3.buildQuery(inventory.inventoryNumberPart3));
		}
		if (autoDeducted != null) {
			predicates.add(inventory.isAutoDeducted.eq(convertToString(autoDeducted)));
		}
		if (available != null) {
			predicates.add(inventory.isAvailable.eq(convertToString(available)));
		}
		if (distributable != null) {
			predicates.add(inventory.isDistributable.eq(convertToString(distributable)));
		}
		if (latitude != null) {
			predicates.add(latitude.buildQuery(inventory.latitude));
		}
		if (longitude != null) {
			predicates.add(longitude.buildQuery(inventory.longitude));
		}
		if (note != null) {
			predicates.add(note.buildQuery(inventory.note));
		}
		if (parentInventory != null) {
			predicates.add(inventory.parentInventory().id.eq(parentInventory));
		}
		if (CollectionUtils.isNotEmpty(pathogenStatusCode)) {
			predicates.add(inventory.pathogenStatusCode.in(pathogenStatusCode));
		}
		if (CollectionUtils.isNotEmpty(plantSexCode)) {
			predicates.add(inventory.plantSexCode.in(plantSexCode));
		}
		if (regenerationMethod != null) {
			predicates.addAll(regenerationMethod.collectPredicates(inventory.regenerationMethod()));
		}
		if (preservationMethod != null) {
			predicates.addAll(preservationMethod.collectPredicates(inventory.preservationMethod()));
		}
		if (inventoryMaintenancePolicy != null) {
			predicates.addAll(inventoryMaintenancePolicy.collectPredicates(inventory.inventoryMaintenancePolicy()));
		}
		if (CollectionUtils.isNotEmpty(pollinationMethodCode)) {
			predicates.add(inventory.pollinationMethodCode.in(pollinationMethodCode));
		}
		if (CollectionUtils.isNotEmpty(pollinationVectorCode)) {
			predicates.add(inventory.pollinationVectorCode.in(pollinationVectorCode));
		}
		if (propagationDate != null) {
			predicates.add(propagationDate.buildQuery(inventory.propagationDate));
		}
		if (CollectionUtils.isNotEmpty(propagationDateCode)) {
			predicates.add(inventory.propagationDateCode.in(propagationDateCode));
		}
		if (quantityOnHand != null) {
			predicates.add(quantityOnHand.buildQuery(inventory.quantityOnHand));
		}
		if (CollectionUtils.isNotEmpty(quantityOnHandUnitCode)) {
			predicates.add(inventory.quantityOnHandUnitCode.in(quantityOnHandUnitCode));
		}
		if (regenerationCriticalQuantity != null) {
			predicates.add(regenerationCriticalQuantity.buildQuery(inventory.regenerationCriticalQuantity));
		}
		if (rootstock != null) {
			predicates.add(rootstock.buildQuery(inventory.rootstock));
		}
		if (CollectionUtils.isNotEmpty(barcode)) {
			predicates.add(inventory.barcode.in(barcode));
		}
		if (storageLocationPart1 != null) {
			predicates.add(storageLocationPart1.buildQuery(inventory.storageLocationPart1));
		}
		if (storageLocationPart2 != null) {
			predicates.add(storageLocationPart2.buildQuery(inventory.storageLocationPart2));
		}
		if (storageLocationPart3 != null) {
			predicates.add(storageLocationPart3.buildQuery(inventory.storageLocationPart3));
		}
		if (storageLocationPart4 != null) {
			predicates.add(storageLocationPart4.buildQuery(inventory.storageLocationPart4));
		}
		if (webAvailabilityNote != null) {
			predicates.add(webAvailabilityNote.buildQuery(inventory.webAvailabilityNote));
		}
		if (CollectionUtils.isNotEmpty(accessionInvGroup)) {
			predicates.add(inventory.accessionInvGroupMaps.any().accessionInvGroup().id.in(accessionInvGroup));
		}
		if (actions != null) {
			var qInventoryAction = new QInventoryAction(inventory.actions.getMetadata().getName());
			predicates.add(nestedFilter(inventory.actions, qInventoryAction, qInventoryAction.inventory().eq(inventory), actions.collectPredicates(qInventoryAction)));
		}
		if (names != null) {
			predicates.addAll(names.collectPredicates(inventory.names.any()));
		}
		if (extra != null) {
			predicates.addAll(extra.collectPredicates(inventory.extra()));
		}
		if (cropTraits != null) {
			if (StringUtils.isNotBlank(_text)) throw new InvalidApiUsageException("_text search is not available when filtering cropTrats");
			predicates.add(inventory.id.in(processCropTraitObservations(cropTraits)));
		}
		if (productionLocationGeography != null) {
			predicates.addAll(productionLocationGeography.collectPredicates(inventory.productionLocationGeography()));
		}
		if (plantName != null) {
			predicates.add(plantName.buildQuery(inventory.names.any().plantName));
		}

		return predicates;
	}

	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; 
		}

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

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

	protected void configExtraNullAndNotNull() {
		var extraPropName = QInventory.inventory.extra().getMetadata().getName().concat(".");
		if (CollectionUtils.isNotEmpty(NULL)) {
			var extraProps = NULL.stream().filter(nullProp -> nullProp.startsWith(extraPropName)).collect(Collectors.toList());
			if (!extraProps.isEmpty()) {
				if (extra == null) {
					extra = new InventoryExtraFilter();
				}
				if (extra.NULL == null) {
					extra.NULL(new HashSet<>());
				}
				extra.NULL.addAll(extraProps.stream().map(extraProp -> extraProp.replaceFirst(extraPropName, "")).collect(Collectors.toSet()));
				extraProps.forEach(NULL::remove);
			}
		}
		if (CollectionUtils.isNotEmpty(NOTNULL)) {
			var extraProps = NOTNULL.stream().filter(nullProp -> nullProp.startsWith(extraPropName)).collect(Collectors.toList());
			if (!extraProps.isEmpty()) {
				if (extra == null) {
					extra = new InventoryExtraFilter();
				}
				if (extra.NOTNULL == null) {
					extra.NOTNULL(new HashSet<>());
				}
				extra.NOTNULL.addAll(extraProps.stream().map(extraProp -> extraProp.replaceFirst(extraPropName, "")).collect(Collectors.toSet()));
				extraProps.forEach(NOTNULL::remove);
			}
		}
	}
}