InventoryViabilityController.java
/*
* Copyright 2021 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.api.v1.impl;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import io.swagger.v3.oas.annotations.Operation;
import org.apache.commons.lang3.ArrayUtils;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.v1.ActionController;
import org.gringlobal.api.v1.FilteredCRUDController;
import org.gringlobal.api.v1.FilteredPage;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.AbstractAction.ActionState;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.InventoryAction;
import org.gringlobal.model.InventoryViability;
import org.gringlobal.model.InventoryViabilityAction;
import org.gringlobal.model.InventoryViabilityAttach;
import org.gringlobal.model.InventoryViabilityData;
import org.gringlobal.model.InventoryViabilityRule;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.QInventoryAction;
import org.gringlobal.model.QInventoryViability;
import org.gringlobal.model.QInventoryViabilityAction;
import org.gringlobal.model.Site;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.persistence.InventoryRepository;
import org.gringlobal.persistence.InventoryViabilityRuleRepository;
import org.gringlobal.service.InventoryActionService;
import org.gringlobal.service.InventoryService;
import org.gringlobal.service.InventoryViabilityActionService;
import org.gringlobal.service.InventoryViabilityActionService.InventoryViabilityActionRequest;
import org.gringlobal.service.InventoryViabilityActionService.InventoryViabilityActionScheduleFilter;
import org.gringlobal.service.InventoryViabilityAttachmentService;
import org.gringlobal.service.InventoryViabilityDataService;
import org.gringlobal.service.InventoryViabilityService;
import org.gringlobal.service.InventoryViabilityService.InventoryViabilityDetails;
import org.gringlobal.service.filter.InventoryActionFilter;
import org.gringlobal.service.filter.InventoryFilter;
import org.gringlobal.service.filter.InventoryViabilityActionFilter;
import org.gringlobal.service.filter.InventoryViabilityAttachFilter;
import org.gringlobal.service.filter.InventoryViabilityDataFilter;
import org.gringlobal.service.filter.InventoryViabilityFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import com.querydsl.core.types.OrderSpecifier;
import org.springdoc.api.annotations.ParameterObject;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.web.multipart.MultipartFile;
@RestController("inventoryViabilityApi1")
@RequestMapping(InventoryViabilityController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = InventoryViabilityController.API_TAG)
public class InventoryViabilityController extends FilteredCRUDController<InventoryViability, InventoryViabilityService, InventoryViabilityFilter> {
/** The Constant API_URL. */
public static final String API_URL = InventoryController.API_URL + "/viability";
public static final String API_TAG = "InventoryViability";
@Autowired
private InventoryService inventoryService;
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private InventoryActionService inventoryActionService;
@Autowired
private InventoryViabilityRuleRepository inventoryViabilityRuleRepository;
@Autowired
private InventoryViabilityAttachmentService attachmentFileService;
@Override
protected Class<InventoryViabilityFilter> filterType() {
return InventoryViabilityFilter.class;
}
@Override
protected OrderSpecifier<?>[] defaultSort() {
return new OrderSpecifier[] { QInventoryViability.inventoryViability.testedDate.desc() };
}
@Override
public InventoryViability create(@RequestBody InventoryViability entity) {
entity.setInventory(inventoryService.get(entity.getInventory().getId()));
if (entity.getInventoryViabilityRule() != null) {
entity.setInventoryViabilityRule(inventoryViabilityRuleRepository.findById(entity.getInventoryViabilityRule().getId()).orElseThrow());
}
return super.create(entity);
}
@PostMapping("/scheduled")
public FilteredPage<InventoryAction, InventoryFilter> listScheduledInventories(@ParameterObject final Pagination page, @RequestBody(required = false) final InventoryFilter inventoryFilter) throws SearchException, IOException {
InventoryFilter normalizedFilter = shortFilterService.normalizeFilter(inventoryFilter, InventoryFilter.class);
InventoryActionFilter actionFilter = (InventoryActionFilter) new InventoryActionFilter()
.inventory(normalizedFilter)
.actionNameCode(Set.of(CommunityCodeValues.INVENTORY_ACTION_VABILITYTEST.value))
.states(Set.of(ActionState.PENDING, ActionState.SCHEDULED));
Pageable pageable = ArrayUtils.isEmpty(page.getS()) ? page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, QInventoryAction.inventoryAction.notBeforeDate.asc().nullsFirst()) : page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
return new FilteredPage<>(normalizedFilter, inventoryActionService.listActions(actionFilter, pageable));
}
@PostMapping("/order")
public OrderRequest orderViabilityTest(@RequestBody @Valid ViabilityOrderRequest viabilityOrderRequest) {
List<Inventory> inventories = inventoryRepository.findAllById(viabilityOrderRequest.inventories.keySet());
if (inventories.size() != viabilityOrderRequest.inventories.size()) {
throw new InvalidApiUsageException("Specified inventory does not exist");
}
var viabilityRuleIds = Set.copyOf(viabilityOrderRequest.inventories.values());
List<InventoryViabilityRule> viabilityRules = inventoryViabilityRuleRepository.findAllById(viabilityRuleIds);
if (viabilityRules.size() != viabilityRuleIds.size()) {
throw new InvalidApiUsageException("Specified vability rule does not exist");
}
var inventoryLookup = inventories.stream().collect(Collectors.toMap(Inventory::getId, Function.identity()));
var ruleLookup = viabilityRules.stream().collect(Collectors.toMap(InventoryViabilityRule::getId, Function.identity()));
Map<Inventory, InventoryViabilityRule> inventoriesAndRules = viabilityOrderRequest.inventories.entrySet().stream()
// map
.collect(Collectors.toMap((entry) -> inventoryLookup.get(entry.getKey()), (entry) -> ruleLookup.get(entry.getValue())));
return crudService.orderViabilityTest(viabilityOrderRequest.site, inventoriesAndRules, viabilityOrderRequest.cooperator);
}
/**
* Calculate results across all replicates.
*
* @param id the id of InventoryViability
* @return object of InventoryViability (not persisted) with calculated results
*/
@PostMapping("/{id:\\d+}/calculate")
public InventoryViabilityDetails calculate(@PathVariable("id") final long id, @RequestBody(required = false) List<Integer> selectedReplicationNumbers) {
return crudService.calculateResult(crudService.get(id), selectedReplicationNumbers);
}
/**
* Returns fully loaded viability + data records
* @param id InventoryViability#id
* @return fully loaded viability
*/
@GetMapping(ENDPOINT_ID + "/details")
public InventoryViabilityDetails getDetails(@PathVariable("id") final long id) {
return crudService.loadDetails(crudService.get(id));
}
@RestController("inventoryViabilityActionApi1")
@RequestMapping(InventoryViabilityActionController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = InventoryViabilityController.API_TAG)
public static class InventoryViabilityActionController extends ActionController<InventoryViabilityAction, InventoryViabilityActionFilter, InventoryViabilityActionRequest, InventoryViabilityActionScheduleFilter, InventoryViabilityActionService> {
public static final String API_URL = InventoryViabilityController.API_URL;
@Override
protected Class<InventoryViabilityActionFilter> filterType() {
return InventoryViabilityActionFilter.class;
}
@Override
protected OrderSpecifier<?>[] defaultSort() {
return new OrderSpecifier[] { QInventoryViabilityAction.inventoryViabilityAction.startedDate.desc() };
}
}
@RestController("inventoryViabilityDataApi1")
@RequestMapping(InventoryViabilityDataController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = InventoryViabilityController.API_TAG)
public static class InventoryViabilityDataController extends FilteredCRUDController<InventoryViabilityData, InventoryViabilityDataService, InventoryViabilityDataFilter> {
public static final String API_URL = InventoryViabilityController.API_URL + "/data";
}
@NoArgsConstructor
@SuperBuilder
public static class ViabilityOrderRequest {
public Site site;
@NotNull
public Cooperator cooperator;
@NotNull
@Size(min = 1)
public Map<Long, Long> inventories;
}
@PostMapping(value = "/attach/{inventoryViabilityId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "uploadFile", description = "Attach file", summary = "Attach file")
public InventoryViabilityAttach uploadFile(@PathVariable(name = "inventoryViabilityId") final Long inventoryViabilityId, @RequestPart(name = "file") final MultipartFile file,
@RequestPart(name = "metadata") final InventoryViabilityAttachmentService.InventoryViabilityAttachmentRequest metadata) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return attachmentFileService.uploadFile(crudService.get(inventoryViabilityId), file, metadata);
}
@DeleteMapping(value = "/attach/{inventoryViabilityId}/{attachmentId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "removeFile", description = "Remove attached file", summary = "Remove file")
public InventoryViabilityAttach removeFile(@PathVariable(name = "inventoryViabilityId") final Long inventoryViabilityId, @PathVariable(name = "attachmentId") final Long attachmentId) {
return attachmentFileService.removeFile(crudService.get(inventoryViabilityId), attachmentId);
}
@GetMapping(value = "/attach/{inventoryViabilityId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "listAttachments", description = "List attached files", summary = "list files")
public List<InventoryViabilityAttach> listFiles(@PathVariable(name = "inventoryViabilityId") final Long inventoryViabilityId) {
return attachmentFileService.getAllAttachments(crudService.get(inventoryViabilityId));
}
@RestController("inventoryViabilityAttachApi1")
@RequestMapping(InventoryViabilityController.InventoryViabilityAttachController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = InventoryViabilityController.API_TAG)
public static class InventoryViabilityAttachController extends FilteredCRUDController<InventoryViabilityAttach, InventoryViabilityAttachmentService, InventoryViabilityAttachFilter> {
/** The Constant API_URL. */
public static final String API_URL = InventoryViabilityController.API_URL + "/attach/meta";
@Override
@Operation(operationId = "createInventoryViabilityAttach", description = "Create InventoryViabilityAttach", summary = "Create")
public InventoryViabilityAttach create(@RequestBody InventoryViabilityAttach entity) {
return super.create(entity);
}
@Override
@Operation(operationId = "updateInventoryViabilityAttach", description = "Update an existing record", summary = "Update")
public InventoryViabilityAttach update(@RequestBody InventoryViabilityAttach entity) {
return super.update(entity);
}
@Override
@Operation(operationId = "getInventoryViabilityAttach", description = "Get record by ID", summary = "Get")
public InventoryViabilityAttach get(@PathVariable long id) {
return super.get(id);
}
@Override
@Operation(operationId = "deleteInventoryViabilityAttach", description = "Delete existing record by ID", summary = "Delete")
public InventoryViabilityAttach remove(@PathVariable long id) {
return super.remove(id);
}
}
}