DescriptorListController.java
/*
* Copyright 2018 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.v1;
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.genesys.blocks.model.JsonViews;
import org.genesys.server.api.ApiBaseController;
import org.genesys.server.api.FilteredPage;
import org.genesys.server.api.Pagination;
import org.genesys.server.api.v1.facade.DescriptorApiService;
import org.genesys.server.api.v1.facade.DescriptorListApiService;
import org.genesys.server.api.v1.model.Descriptor;
import org.genesys.server.api.v1.model.DescriptorList;
import org.genesys.server.api.v1.model.DescriptorListSuggestionPage;
import org.genesys.server.component.aspect.DownloadEndpoint;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.PublishState;
import org.genesys.server.model.filters.DescriptorListFilter;
import org.genesys.server.service.DescriptorListService;
import org.genesys.server.service.ElasticsearchService;
import org.genesys.server.service.ShortFilterService;
import org.genesys.server.service.DescriptorListService.DescriptorListOverview;
import org.genesys.server.service.ElasticsearchService.TermResult;
import org.genesys.server.service.ShortFilterService.FilterInfo;
import org.genesys.server.exception.SearchException;
import org.genesys.server.service.worker.DescriptorListExporter;
import org.genesys.server.service.worker.ShortFilterProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.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 com.fasterxml.jackson.annotation.JsonView;
import com.google.common.collect.Sets;
import io.swagger.annotations.Api;
/**
* The Class DescriptorListController.
*
* @author Matija Obreza
*/
@RestController("descriptorListApi1")
@RequestMapping(org.genesys.server.api.v1.DescriptorListController.CONTROLLER_URL)
@PreAuthorize("isAuthenticated()")
@Api(tags = { "descriptor-list" })
public class DescriptorListController extends ApiBaseController {
/** The Constant LOG. */
private static final Logger LOG = LoggerFactory.getLogger(DescriptorListController.class);
/** The Constant CONTROLLER_URL. */
public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/descriptorlist";
private final Set<String> terms = Sets.newHashSet("owner.uuid", "crop");
@Autowired(required = false)
private ElasticsearchService elasticsearchService;
@Autowired
private DescriptorListService descriptorListService;
@Autowired
private DescriptorListApiService apiService;
@Autowired
private DescriptorApiService descriptorApiService;
@Autowired
private DescriptorListExporter exporter;
/** The short filter service. */
@Autowired
protected ShortFilterProcessor shortFilterProcessor;
/**
* Gets the descriptor list.
*
* @param uuid the uuid
* @return the descriptor list
*/
@GetMapping(value = "/{uuid}")
@JsonView(JsonViews.Public.class)
public DescriptorList getDescriptorList(@PathVariable("uuid") final UUID uuid) {
return apiService.loadDescriptorList(uuid);
}
@GetMapping(value = "/{uuid}/descriptors")
@JsonView(JsonViews.Public.class)
public List<Descriptor> getDescriptors(@PathVariable("uuid") final UUID uuid) {
return apiService.loadDescriptors(apiService.loadDescriptorList(uuid));
}
/**
* Delete descriptor list.
*
* @param uuid the uuid
* @param version the version
* @return the descriptor list
*/
@DeleteMapping(value = "/{uuid},{version}")
public DescriptorList deleteDescriptorList(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version) {
return apiService.remove(apiService.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}/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
*/
@JsonView(JsonViews.Minimal.class)
@GetMapping(value = "/autocomplete", produces = MediaType.APPLICATION_JSON_VALUE)
public List<DescriptorList> autocomplete(@RequestParam("d") final String text) {
return apiService.autocompleteDescriptorLists(text);
}
/**
* Creates the descriptor list.
*
* @param source the source
* @return the descriptor list
*/
@PostMapping(value = "/create")
@JsonView(JsonViews.Public.class)
public DescriptorList createDescriptorList(@RequestBody final DescriptorList source) {
return apiService.create(source);
}
/**
* Update descriptor list.
*
* @param source the source
* @return the descriptor list
*/
@PostMapping(value = "/update")
@JsonView(JsonViews.Public.class)
public DescriptorList updateDescriptorList(@RequestBody final DescriptorList source) {
return apiService.update(source);
}
/**
* List descriptor lists.
*
* @param filterCode short filter code -- overrides filter in body
* @param page the page
* @param filter the descriptor list filter
* @return the page
* @throws IOException Signals that an I/O exception has occurred.
*/
@PostMapping(value = "/list")
@JsonView(JsonViews.Public.class)
public FilteredPage<DescriptorList, DescriptorListFilter> listDescriptorLists(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) DescriptorListFilter filter) throws IOException, SearchException {
FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, apiService.listDescriptorLists(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE)));
}
/**
* 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.
*/
@PostMapping(value = "/filter")
public DescriptorListSuggestionPage listSuggestions(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) DescriptorListFilter filter) throws IOException, SearchException {
FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
var pageRes = new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, apiService.listDescriptorLists(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE)));
Map<String, ElasticsearchService.TermResult> suggestionRes = descriptorListService.getSuggestions(filterInfo.filter);
return 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 })
@JsonView({ JsonViews.Public.class })
public 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 {
FilterInfo<DescriptorListFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, DescriptorListFilter.class);
filterInfo.filter.state(Set.of(PublishState.PUBLISHED));
Map<String, 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 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 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.");
}
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")
@JsonView(JsonViews.Public.class)
public FilteredPage<DescriptorList, 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, apiService.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},{version}")
@JsonView(JsonViews.Public.class)
public DescriptorList addDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final List<UUID> descriptorUuids) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
final List<Descriptor> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toList());
return apiService.addDescriptors(descriptorList, descriptors.toArray(new Descriptor[] {}));
}
/**
* 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},{version}")
@JsonView(JsonViews.Public.class)
public DescriptorList removeDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final Set<UUID> descriptorUuids) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
final Set<Descriptor> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toSet());
return apiService.removeDescriptors(descriptorList, descriptors.toArray(new Descriptor[] {}));
}
/**
* 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},{version}")
@JsonView(JsonViews.Public.class)
public DescriptorList setDescriptor(@PathVariable("uuid") final UUID uuid, @PathVariable("version") final int version, @RequestBody final List<UUID> descriptorUuids) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
final List<Descriptor> descriptors = descriptorUuids.stream().map(descriptorUuid -> descriptorApiService.getDescriptor(descriptorUuid)).collect(Collectors.toList());
return apiService.setDescriptors(descriptorList, descriptors.toArray(new Descriptor[] {}));
}
/**
* 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)
@JsonView(JsonViews.Public.class)
public DescriptorList approveDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
return apiService.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)
@JsonView(JsonViews.Public.class)
public DescriptorList reviewDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
return apiService.reviewDescriptorList(descriptorList);
}
@DownloadEndpoint
@RequestMapping(value = "{uuid}/download", method = RequestMethod.POST)
public void download(@PathVariable("uuid") final UUID uuid, HttpServletResponse response) throws IOException, NotFoundElement {
final DescriptorList descriptorList = apiService.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 {
apiService.exportDescriptorList(descriptorList, 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)
@JsonView(JsonViews.Public.class)
public DescriptorList rejectDescriptorList(@RequestParam(value = "uuid", required = true) final UUID uuid, @RequestParam(value = "version", required = true) final int version) {
final DescriptorList descriptorList = apiService.loadDescriptorList(uuid, version);
return apiService.rejectDescriptorList(descriptorList);
}
}