DescriptorListController.java
/*
* Copyright 2024 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.genesys.server.api.v2.impl;
import com.google.common.collect.Sets;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.genesys.server.api.ApiBaseController;
import org.genesys.server.api.FilteredPage;
import org.genesys.server.api.Pagination;
import org.genesys.server.api.v2.FilteredCRUDController;
import org.genesys.server.api.v2.TranslatedCRUDController;
import org.genesys.server.api.v2.facade.DescriptorApiService;
import org.genesys.server.api.v2.facade.DescriptorListApiService;
import org.genesys.server.api.v2.facade.DescriptorListLangApiService;
import org.genesys.server.api.v2.model.impl.DescriptorDTO;
import org.genesys.server.api.v2.model.impl.DescriptorListDTO;
import org.genesys.server.api.v2.model.impl.DescriptorListInfo;
import org.genesys.server.api.v2.model.impl.DescriptorListLangDTO;
import org.genesys.server.api.v2.model.impl.TranslatedDescriptorDTO;
import org.genesys.server.api.v2.model.impl.TranslatedDescriptorListDTO;
import org.genesys.server.component.aspect.DownloadEndpoint;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.PublishState;
import org.genesys.server.model.filters.DescriptorListFilter;
import org.genesys.server.model.traits.DescriptorList;
import org.genesys.server.model.traits.DescriptorListLang;
import org.genesys.server.service.DescriptorListService;
import org.genesys.server.service.ElasticsearchService;
import org.genesys.server.service.ShortFilterService;
import org.genesys.server.service.TranslatorService;
import org.genesys.server.service.filter.DescriptorListLangFilter;
import org.genesys.server.service.worker.DescriptorListExporter;
import org.genesys.server.service.worker.ShortFilterProcessor;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@RestController("descriptorListApi2")
@RequestMapping(DescriptorListController.CONTROLLER_URL)
@Tag(name = "DescriptorList")
public class DescriptorListController extends TranslatedCRUDController<DescriptorListDTO, TranslatedDescriptorListDTO,
DescriptorListLangDTO, DescriptorList, DescriptorListLang, DescriptorListApiService, DescriptorListFilter> {
/** The Constant CONTROLLER_URL. */
public static final String CONTROLLER_URL = ApiBaseController.APIv2_BASE + "/descriptorlist";
private final Set<String> terms = Sets.newHashSet("owner.uuid", "crop");
@Autowired
private DescriptorListExporter exporter;
@Autowired
private DescriptorListService descriptorListService;
@Autowired
protected ShortFilterProcessor shortFilterProcessor;
@Autowired(required = false)
private ElasticsearchService elasticsearchService;
@Autowired
private DescriptorApiService descriptorApiService;
/**
* Gets the descriptor list.
*
* @param uuid the uuid
* @return the descriptor list
*/
@GetMapping(value = "/{uuid:\\w{8}\\-\\w{4}.+}")
public TranslatedDescriptorListDTO getDescriptorList(@PathVariable("uuid") final UUID uuid) {
return translatedApiService.loadTranslatedDescriptorList(uuid);
}
@GetMapping(value = "/{uuid:\\w{8}\\-\\w{4}.+}/descriptors")
public List<TranslatedDescriptorDTO> getDescriptors(@PathVariable("uuid") final UUID uuid) {
return translatedApiService.loadDescriptors(uuid);
}
/**
* Delete descriptor list.
*
* @param uuid the uuid
* @param version the version
* @return the descriptor list
*/
@DeleteMapping(value = "/{uuid:\\w{8}\\-\\w{4}.+},{version}")
public DescriptorListDTO deleteDescriptorList(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version) {
return translatedApiService.remove(translatedApiService.loadDescriptorList(uuid, version));
}
/**
* Generate document.
*
* @param uuid the uuid
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@Transactional(readOnly = true)
@GetMapping(value = "/{uuid:\\w{8}\\-\\w{4}.+}/html", produces = { "text/html" })
public void generateDocument(@PathVariable("uuid") final UUID uuid, final HttpServletRequest request, final HttpServletResponse response) throws IOException {
var descriptorList = descriptorListService.loadDescriptorList(uuid);
final String descriptorListHtml = exporter.htmlDescriptorList(descriptorList);
String eTag = descriptorList.getUuid() + "-" + descriptorList.getVersion();
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=86400, s-maxage=86400, public, no-transform");
response.setDateHeader(HttpHeaders.LAST_MODIFIED, descriptorList.getLastModifiedDate().toEpochMilli());
response.setHeader(HttpHeaders.ETAG, eTag);
if (eTag.equals(request.getHeader(HttpHeaders.IF_NONE_MATCH))) {
response.setStatus(HttpStatus.NOT_MODIFIED.value());
response.flushBuffer();
return;
}
long sinceDate = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (sinceDate >= -1 && descriptorList.getLastModifiedDate().toEpochMilli() < sinceDate) {
response.setStatus(HttpStatus.NOT_MODIFIED.value());
response.flushBuffer();
return;
}
response.setHeader(HttpHeaders.PRAGMA, "");
response.setContentType("text/html");
// response.addHeader("Content-Disposition", String.format("attachment;
// filename=\"%s.html\"", descriptorList.getTitle()));
response.getWriter().write(descriptorListHtml);
response.flushBuffer();
}
/**
* Autocomplete.
*
* @param text the text
* @return the list
*/
@GetMapping(value = "/autocomplete", produces = MediaType.APPLICATION_JSON_VALUE)
public List<DescriptorListInfo> autocomplete(@RequestParam("d") final String text) {
return translatedApiService.autocompleteDescriptorLists(text);
}
/**
* Creates the descriptor list.
*
* @param source the source
* @return the descriptor list
*/
@PostMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "create", description = "Create a record with translation", summary = "Add")
public DescriptorListDTO create(@RequestBody final DescriptorListDTO source) {
return super.create(source);
}
/**
* Update descriptor list.
*
* @param source the source
* @return the descriptor list
*/
@PutMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "update", description = "Update an existing record", summary = "Update")
public DescriptorListDTO update(@RequestBody final DescriptorListDTO source) {
return super.update(source);
}
/**
* List descriptor lists with suggestions.
*
* @param filterCode short filter code -- overrides filter in body
* @param page the page
* @param filter the descriptor list filter
* @return the page with suggestions
* @throws IOException Signals that an I/O exception has occurred.
*/
@Override
@PostMapping(value = "/filter")
public DescriptorListApiService.DescriptorListSuggestionPage filter(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) DescriptorListFilter filter) throws IOException, SearchException {
ShortFilterService.FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
var pageRes = new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, translatedApiService.listFiltered(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE)));
Map<String, ElasticsearchService.TermResult> suggestionRes = descriptorListService.getSuggestions(filterInfo.filter);
return DescriptorListApiService.DescriptorListSuggestionPage.from(pageRes, suggestionRes);
}
/**
* Get term overview for filters
*
* @param filterCode short filter code
* @param filter the filter
* @return the overview
* @throws SearchException
*/
@PostMapping(value = "/overview", produces = { MediaType.APPLICATION_JSON_VALUE })
public DescriptorListService.DescriptorListOverview overview(@RequestParam(name = "f", required = false) final String filterCode, @RequestBody(required = false) final DescriptorListFilter filter,
@RequestParam(name = "limit", defaultValue = "10", required = false) final int limit) throws IOException, SearchException {
ShortFilterService.FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
filterInfo.filter.state(Set.of(PublishState.PUBLISHED));
Map<String, ElasticsearchService.TermResult> overview = elasticsearchService.termStatisticsAuto(org.genesys.server.model.traits.DescriptorList.class, filterInfo.filter, Math.min(50, limit), terms.toArray(new String[] {}));
long descriptorListCount = descriptorListService.countDescriptorLists(filterInfo.filter);
Map<String, ElasticsearchService.TermResult> suggestionRes = descriptorListService.getSuggestions(filterInfo.filter);
return DescriptorListService.DescriptorListOverview.from(filterInfo.filterCode, filterInfo.filter, overview, descriptorListCount, suggestionRes);
}
/**
* Load more data for the specified term
*
* @param filterCode short filter code
* @param filter the filter
* @param term the term
* @return the term result
* @throws SearchException the search exception
* @throws IOException signals that an I/O exception has occurred
*/
@PostMapping(value = "/overview/{term}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ElasticsearchService.TermResult loadMoreTerms(@PathVariable(name = "term") final String term, @RequestBody(required = false) final DescriptorListFilter filter,
@RequestParam(name = "f", required = false) final String filterCode,
@RequestParam(name = "limit", defaultValue = "20", required = false) final int limit) throws IOException, SearchException {
if (!terms.contains(term)) {
throw new InvalidApiUsageException("No such term. Will not search.");
}
ShortFilterService.FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
filterInfo.filter.state(Set.of(PublishState.PUBLISHED));
return elasticsearchService.termStatisticsAuto(org.genesys.server.model.traits.DescriptorList.class, filterInfo.filter, Math.min(200, limit), term);
}
/**
* My descriptor lists.
*
* @param filterCode the filter code
* @param page the page
* @param filter the filter
* @return the page
* @throws IOException Signals that an I/O exception has occurred.
*/
@PostMapping(value = "/list-mine")
public FilteredPage<DescriptorListDTO, DescriptorListFilter> myDescriptorLists(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) DescriptorListFilter filter) throws IOException, SearchException {
ShortFilterService.FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, translatedApiService.listDescriptorListsForCurrentUser(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.DESC, "lastModifiedDate")));
}
/**
* Add descriptor to descriptor list.
*
* @param uuid the uuid
* @param version the version
* @param descriptorUuids the descriptor uuids
* @return the descriptor list
*/
@PostMapping(value = "/add-descriptors/{uuid:\\w{8}\\-\\w{4}.+},{version}")
public DescriptorListDTO addDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final List<UUID> descriptorUuids) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
final List<DescriptorDTO> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toList());
return translatedApiService.addDescriptors(descriptorList, descriptors.toArray(new DescriptorDTO[] {}));
}
/**
* Remove descriptor from descriptor list.
*
* @param uuid the uuid
* @param version the version
* @param descriptorUuids the descriptor uuids
* @return the descriptor list
*/
@PostMapping(value = "/remove-descriptors/{uuid:\\w{8}\\-\\w{4}.+},{version}")
public DescriptorListDTO removeDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final Set<UUID> descriptorUuids) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
final Set<DescriptorDTO> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toSet());
return translatedApiService.removeDescriptors(descriptorList, descriptors.toArray(new DescriptorDTO[] {}));
}
/**
* Set descriptors in the descriptor list.
*
* @param uuid the uuid
* @param version the version
* @param descriptorUuids the descriptor uuids
* @return the descriptor list
*/
@PostMapping(value = "/set-descriptors/{uuid:\\w{8}\\-\\w{4}.+},{version}")
public DescriptorListDTO setDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final List<UUID> descriptorUuids) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
final List<DescriptorDTO> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toList());
return translatedApiService.setDescriptors(descriptorList, descriptors.toArray(new DescriptorDTO[] {}));
}
/**
* Loads DescriptorList by uuid and version and tries to publish it.
*
* @param uuid descriptorList UUID
* @param version record version
* @return published DescriptorList (admin-only)
*/
@RequestMapping(value = "/approve", method = RequestMethod.POST)
public DescriptorListDTO approveDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
return translatedApiService.approveDescriptorList(descriptorList);
}
/**
* Loads DescriptorList by uuid and version and send to review.
*
* @param uuid descriptorList UUID
* @param version record version
* @return descriptorList in review state
*/
@RequestMapping(value = "/for-review", method = RequestMethod.POST)
public DescriptorListDTO reviewDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
return translatedApiService.reviewDescriptorList(descriptorList);
}
@DownloadEndpoint
@RequestMapping(value = "{uuid:\\w{8}\\-\\w{4}.+}/download", method = RequestMethod.POST)
public void download(@PathVariable("uuid") final UUID uuid, HttpServletResponse response) throws IOException, NotFoundElement {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid);
if (descriptorList == null) {
throw new NotFoundElement();
}
// Write descriptorList metadata to the stream.
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.addHeader("Content-Disposition", String.format("attachment; filename=\"DESCRIPTOR LIST - %1s.xlsx\"", descriptorList.getUuid()));
final OutputStream outputStream = response.getOutputStream();
try {
translatedApiService.exportDescriptorList(uuid, outputStream);
response.flushBuffer();
} catch (EOFException e) {
LOG.warn("Download was aborted: {}", e.getMessage());
throw e;
}
}
/**
* Loads descriptorList by uuid and version and unpublish it.
*
* @param uuid descriptorList UUID
* @param version record version
* @return unpublished descriptorList
*/
@RequestMapping(value = "/reject", method = RequestMethod.POST)
public DescriptorListDTO rejectDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorListDTO descriptorList = translatedApiService.loadDescriptorList(uuid, version);
return translatedApiService.rejectDescriptorList(descriptorList);
}
/**
* Gets machine translation of the DescriptorList
*
* @param uuid the DescriptorList uuid
* @param targetLang target language tag
* @return DescriptorListLangDTO
* @throws org.genesys.server.service.TranslatorService.TranslatorException
*/
@GetMapping(value = "/{uuid:\\w{8}\\-\\w{4}.+}/translate/{targetLang}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "machineTranslateByUuid", summary = "Machine translate", description = "Generate machine translation of the descriptor list by uuid")
public DescriptorListLangDTO machineTranslate(
@PathVariable @Parameter(description = "DescriptorList UUID") final UUID uuid,
@PathVariable @Parameter(description = "Target language tag") final String targetLang
) throws TranslatorService.TranslatorException {
return translatedApiService.machineTranslate(uuid, targetLang);
}
@RestController("descriptorListLangApi2")
@RequestMapping(DescriptorListController.DescriptorListLangController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "DescriptorList")
public static class DescriptorListLangController extends FilteredCRUDController<DescriptorListLangDTO, DescriptorListLang, DescriptorListLangApiService, DescriptorListLangFilter> {
/** The Constant API_URL. */
public static final String API_URL = DescriptorListController.CONTROLLER_URL + "/lang";
}
}