InventoryRepositoryCustomImpl.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.persistence;

import java.util.concurrent.TimeUnit;

import javax.persistence.EntityManager;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.gringlobal.model.Accession;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.QAccession;
import org.gringlobal.model.QInventory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.querydsl.BlazeJPAQuery;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class InventoryRepositoryCustomImpl implements InventoryRepositoryCustom {

	@Autowired
	private CriteriaBuilderFactory criteriaBuilderFactory;

	@Autowired
	private EntityManager entityManager;

	@Autowired
	private JPAQueryFactory jpaQueryFactory;

	/**
	 * Find max number part 2.
	 *
	 * @param accession the accession
	 * @param inventoryNumberPart1 the inventory number part 1
	 * @param inventoryNumberPart3 the inventory number part 3
	 * @param formType the form type
	 * @return the long
	 */
	@Override
	public long findMaxNumberPart2(final Accession accession, final String inventoryNumberPart1, String inventoryNumberPart3, String formType) {
		// BooleanExpression predicate =
		// QInventory.inventory.accession.eq(accession).and(QInventory.inventory.inventoryNumberPart1.eq(numberPart1));

		// We are ignoring accession
		BooleanExpression predicate;
		if (inventoryNumberPart1 != null) {
			predicate = QInventory.inventory.inventoryNumberPart1.eq(inventoryNumberPart1);
		} else {
			predicate = QInventory.inventory.inventoryNumberPart1.isNull();
		}
		if (StringUtils.isNotBlank(inventoryNumberPart3)) {
			predicate = predicate.and(QInventory.inventory.inventoryNumberPart3.eq(inventoryNumberPart3));
		} else {
			predicate = predicate.and(QInventory.inventory.inventoryNumberPart3.isNull());
		}
		if (formType != null) {
			predicate = predicate.and(QInventory.inventory.formTypeCode.eq(formType));
		} else {
			predicate = predicate.and(QInventory.inventory.formTypeCode.isNull());
		}

		final JPAQuery<Long> x = jpaQueryFactory.from(QInventory.inventory).select(QInventory.inventory.inventoryNumberPart2.max()).where(predicate);

		Long max = x.fetchFirst();
		return max == null || max < 0 ? 0 : max.longValue();
	}

	@Override
	public Inventory getSystemInventory(Accession accession) {
		return jpaQueryFactory.selectFrom(QInventory.inventory)
			// accession
			.where(QInventory.inventory.accession().eq(accession)
				// FTC == **
				.and(QInventory.inventory.formTypeCode.eq(Inventory.SYSTEM_INVENTORY_FTC)))
			// One
			.fetchOne();
	}

	@Override
	public void setInventoryNumber(Long inventoryId, String inventoryNumber) {
		jpaQueryFactory.update(QInventory.inventory)
				.where(QInventory.inventory.id.eq(inventoryId))
				.set(QInventory.inventory.inventoryNumber, inventoryNumber)
				.execute();
	}

	@Override
	public Page<AggregatedInventoryQuantity> aggregateQuantity(Predicate filters, Pageable page) {
		assert filters != null;

		var groupByProperties = new com.querydsl.core.types.Path[] {
				QInventory.inventory.accession().id,
				QInventory.inventory.formTypeCode,
				QInventory.inventory.availabilityStatusCode,
				QInventory.inventory.quantityOnHandUnitCode
		};

		var aggQuery = new BlazeJPAQuery<>(entityManager, criteriaBuilderFactory)
				.from(QInventory.inventory)
				.select(Projections.constructor(AggregatedInventoryQuantity.class, QInventory.inventory.accession().id,
						QInventory.inventory.formTypeCode,
						QInventory.inventory.availabilityStatusCode,
						QInventory.inventory.quantityOnHandUnitCode,
						QInventory.inventory.inventoryMaintenancePolicy().id.countDistinct().as("inventoryMaintenancePolicyCount"),
						QInventory.inventory.parentInventory().id.countDistinct().as("parentInventoryCount"),
						QInventory.inventory.id.count().as("inventoryCount"),
						QInventory.inventory.quantityOnHand.sum().as("quantityOnHand")
				))
				// where
				.where(filters)
				// group by
				.groupBy(groupByProperties);

		// get total elements
		var stopWatch = StopWatch.createStarted();
		var totalElements = aggQuery.fetchCount();
		stopWatch.split();
		log.warn("aggregateQuantity count took {}ms", stopWatch.getTime(TimeUnit.MILLISECONDS));
		stopWatch.unsplit();

		// apply pagination
		aggQuery.offset(page.getOffset()).limit(page.getPageSize());

		// apply sorting
		for (Sort.Order o : page.getSort()) {
			if (StringUtils.equalsIgnoreCase(QAccession.accession.id.toString(), o.getProperty())) {
				// sort by accession.id
				aggQuery.orderBy(new OrderSpecifier<>(o.isAscending() ? Order.ASC : Order.DESC, ExpressionUtils.path(String.class, o.getProperty())));
			} else {
				aggQuery.orderBy(new OrderSpecifier<>(o.isAscending() ? Order.ASC : Order.DESC, ExpressionUtils.path(String.class, o.getProperty())));
			}
		}

		var content = aggQuery.fetch();
		stopWatch.split();
		log.warn("aggregateQuantity fetch took {}ms", stopWatch.getTime(TimeUnit.MILLISECONDS));

		return new PageImpl<>(content, page, totalElements);
	}
}