OrderRequestItemServiceImpl.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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.model.OrderRequestItemAction;
import org.gringlobal.model.QAccession;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.QOrderRequestItem;
import org.gringlobal.model.QOrderRequestItemAction;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.model.workflow.WorkflowActionStep;
import org.gringlobal.persistence.OrderRequestItemActionRepository;
import org.gringlobal.persistence.OrderRequestItemRepository;
import org.gringlobal.persistence.OrderRequestRepository;
import org.gringlobal.service.InventoryService;
import org.gringlobal.service.OrderRequestItemActionService;
import org.gringlobal.service.OrderRequestItemActionService.OrderRequestItemActionRequest;
import org.gringlobal.service.OrderRequestItemActionService.OrderRequestItemActionScheduleFilter;
import org.gringlobal.service.OrderRequestItemService;
import org.gringlobal.service.filter.OrderRequestItemActionFilter;
import org.gringlobal.service.filter.OrderRequestItemFilter;
import org.hibernate.Hibernate;
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 org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.querydsl.core.types.EntityPath;
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.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQuery;

/**
 * The OrderRequestItemServiceImpl.
 */
@Transactional(readOnly = true)
@Service
@Slf4j
public class OrderRequestItemServiceImpl extends FilteredCRUDService2Impl<OrderRequestItem, OrderRequestItemFilter, OrderRequestItemRepository> implements OrderRequestItemService {


	@Autowired
	private InventoryService inventoryService;

	@Component
	protected static class ActionSupport extends BaseActionSupport<OrderRequestItem, OrderRequestItemAction, OrderRequestItemActionFilter, OrderRequestItemActionRepository, OrderRequestItemActionRequest, OrderRequestItemActionScheduleFilter>
			implements OrderRequestItemActionService {

		@Autowired
		private OrderRequestRepository orderRequestRepository;

		@Autowired
		private OrderRequestItemRepository orderRequestItemRepository;

		@Override
		protected EntityPath<OrderRequestItem> getOwningEntityPath() {
			return QOrderRequestItemAction.orderRequestItemAction.orderRequestItem();
		}

		@Override
		protected void initializeActionDetails(List<OrderRequestItemAction> actions) {
			actions.forEach(action -> {
				Hibernate.initialize(action.getOrderRequestItem().getInventory());
				Hibernate.initialize(action.getOrderRequestItem().getInventory().getAccession());
			});
		}

		@Override
		protected void applyOwningEntityFilter(OrderRequestItemActionScheduleFilter filter, String owningEntityAlias, List<Predicate> predicates) {
			QOrderRequestItem qOrderRequestItem = new QOrderRequestItem(owningEntityAlias);
			if (predicates != null && filter.orderRequestItem != null) {
				predicates.addAll(filter.orderRequestItem.collectPredicates(qOrderRequestItem));
			}
		}

		@Override
		protected OrderRequestItemAction createAction(OrderRequestItem owningEntity) {
			OrderRequestItemAction action = new OrderRequestItemAction();
			action.setOrderRequestItem(owningEntity);
			return action;
		}
		
		@Override
		protected void updateAction(OrderRequestItemAction action, OrderRequestItemActionRequest request) {
			action.setActionCost(request.actionCost);
			action.setActionInformation(request.actionInformation);
		}

		@Override
		protected OrderRequestItemAction prepareNextWorkflowStepAction(WorkflowActionStep nextStep, OrderRequestItemAction completedAction) {
			OrderRequestItemAction nextAction = new OrderRequestItemAction();
			nextAction.setOrderRequestItem(new OrderRequestItem(completedAction.getOrderRequestItem().getId()));
			return createFast(nextAction);
		}

		@Override
		@Transactional
		public OrderRequestItemAction create(OrderRequestItemAction source) {
			log.debug("Create OrderRequestItemAction. Input data {}", source);
			OrderRequestItemAction entity = new OrderRequestItemAction();
			entity.apply(source);

			OrderRequestItemAction saved = get(repository.save(entity));
			saved.lazyLoad();

			return saved;
		}

		@Override
		protected Iterable<OrderRequestItem> findOwningEntities(Set<Long> id) {
			return orderRequestItemRepository.findAll(QOrderRequestItem.orderRequestItem.id.in(id));
		}
		
		/**
		 * Start an action for all items of the order request
		 */
		@Override
		@Transactional
		public List<OrderRequestItemAction> startActions(OrderRequest orderRequest, String actionCode) {
			OrderRequestItemActionRequest request = new OrderRequestItemActionRequest();

			orderRequest = orderRequestRepository.getReferenceById(orderRequest.getId());
			request.id = orderRequest.getOrderRequestItems().stream()
					// filter for allowed codes
					.filter((ori) -> CommunityCodeValues.ORDER_REQUEST_ITEM_STATUS_PENDING.is(ori.getStatusCode()))
					// to ID
					.map(OrderRequestItem::getId).collect(Collectors.toSet());
			request.actionNameCode = actionCode;

			if (CollectionUtils.isEmpty(request.id)) {
				return new ArrayList<>();
			}
			return super.startAction(request);
		}

		@Override
		public Page<OrderRequestItemAction> listByOrderRequest(OrderRequest orderRequest, Pageable page) {
			assert(orderRequest != null);
			assert(orderRequest.isNew() == false);

			final var orderRequestItemAction = new QOrderRequestItemAction("oria");
			final var orderRequestItem = new QOrderRequestItem("ori");

			var query = jpaQueryFactory.selectFrom(orderRequestItemAction)
					.join(orderRequestItemAction.orderRequestItem(), orderRequestItem).fetchJoin()
					.join(orderRequestItem.inventory()).fetchJoin()
					.leftJoin(orderRequestItemAction.cooperator()).fetchJoin()
					.where(orderRequestItem.orderRequest().eq(orderRequest));

			var totalElements = query.fetchCount();

			// apply sorting
			for (Sort.Order o : page.getSort()) {
				query.orderBy(new OrderSpecifier<>(o.isAscending() ? Order.ASC : Order.DESC, ExpressionUtils.path(String.class, orderRequestItemAction, o.getProperty())));
			}

			// apply pagination
			if (page.isPaged()) {
				query.offset(page.getOffset()).limit(page.getPageSize());
			}
			return new PageImpl<>(query.fetch(), page, totalElements);
		}
	}

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

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

	@Override
	protected JPAQuery<OrderRequestItem> entityListQuery() {
		QInventory inventory = QInventory.inventory;
		QAccession accession = QAccession.accession;
		return jpaQueryFactory.selectFrom(QOrderRequestItem.orderRequestItem)
			//
			.leftJoin(QOrderRequestItem.orderRequestItem.sourceCooperator()).fetchJoin()
			.leftJoin(QOrderRequestItem.orderRequestItem.webOrderRequestItem()).fetchJoin()
			.leftJoin(QOrderRequestItem.orderRequestItem.inventory(), inventory).fetchJoin()
			.leftJoin(inventory.site()).fetchJoin()
			// owner
			.leftJoin(inventory.accession(), accession).fetchJoin()
			.leftJoin(accession.taxonomySpecies()).fetchJoin();
	}

	@Override
	@Transactional
	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	public OrderRequestItem createFast(OrderRequestItem source) {
		return super.createFast(source);
	}

	@Override
	@Transactional
	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	public OrderRequestItem create(OrderRequestItem source) {
		log.debug("Create order request item. Input data {}", source);
		var orderRequestItem = new OrderRequestItem();
		orderRequestItem.apply(source);
		var saved = repository.save(orderRequestItem);
		// TODO Permission check
		return _lazyLoad(saved);
	}

	@Override
	@Transactional
	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	public OrderRequestItem updateFast(@NotNull @Valid OrderRequestItem updated, OrderRequestItem target) {
		if (Objects.isNull(updated.getInventory())) {
			throw new InvalidApiUsageException("OrderRequestItem does not specify inventory");
		}
		updated.setInventory(inventoryService.get(updated.getInventory().getId()));
		
		if (updated.getWithdrawnInventory() != null) {
			updated.setWithdrawnInventory(inventoryService.get(updated.getWithdrawnInventory().getId()));
		}
		// save order request
		updated.setOrderRequest(target.getOrderRequest());
		target.apply(updated);
		assert(target.getInventory().equals(updated.getInventory()));
		
		var saved = repository.save(target);
		return saved;
	}

	@Override
	@Transactional
	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	public OrderRequestItem update(OrderRequestItem input, OrderRequestItem target) {
		log.debug("Update OrderRequestItem. Input data {}", input);

		if (Objects.isNull(input.getInventory())) {
			throw new InvalidApiUsageException("OrderRequestItem does not specify inventory");
		}

		input.setInventory(inventoryService.get(input.getInventory().getId()));

		if (input.getWithdrawnInventory() != null) {
			input.setWithdrawnInventory(inventoryService.get(input.getWithdrawnInventory().getId()));
		}
		// save order request
		input.setOrderRequest(target.getOrderRequest());
		target.apply(input);

		var saved = repository.save(target);
		return _lazyLoad(saved);
	}

	@Override
	@Transactional
	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	public List<OrderRequestItem> updateAll(Collection<OrderRequestItem> items) {
		if (CollectionUtils.isEmpty(items))
			return Collections.emptyList();

		List<OrderRequestItem> result = new ArrayList<>(items.size());
		for (OrderRequestItem item : items) {
			if (item.isNew())
				throw new InvalidApiUsageException("Unable to update non-existing OrderRequestItem.");

			OrderRequestItem target = reload(item);
			result.add(update(item, target));
		}

		return result;
	}

	@Override
	protected void prepareLabelContext(Map<String, Object> context, OrderRequestItem entity) {
		context.put("orderRequestItem", entity);
		context.put("orderRequest", entity.getOrderRequest());
		context.put("inventory", entity.getInventory());
		context.put("withdrawnInventory", entity.getWithdrawnInventory());
	}
}