RequestsController.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.IOException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.security.SecurityContextUtil;
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.RequestApiService;
import org.genesys.server.api.v1.model.MaterialRequest;
import org.genesys.server.api.v1.model.MaterialSubRequest;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.model.impl.FaoInstitute;
import org.genesys.server.service.InstituteService;
import org.genesys.server.service.RequestService;
import org.genesys.server.service.ShortFilterService;
import org.genesys.server.service.RequestService.NoPidException;
import org.genesys.server.service.ShortFilterService.FilterInfo;
import org.genesys.server.service.TokenVerificationService;
import org.genesys.server.service.filter.MaterialRequestFilter;
import org.genesys.server.service.filter.MaterialSubRequestFilter;
import org.genesys.server.exception.EasySMTAException;
import org.genesys.server.service.worker.ShortFilterProcessor;
import org.genesys.spring.CaptchaChecker;
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.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
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 io.swagger.annotations.Api;
@RestController("requestsApi1")
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = { RequestsController.CONTROLLER_URL })
@Api(tags = { "request" })
@Validated
public class RequestsController extends ApiBaseController {
// Rest controller base URL
public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/requests";
public static final String PARAM_KEY = "key";
public static final String PARAM_TOKENUUID = "tokenUuid";
@Autowired
private RequestApiService requestService;
@Autowired
private InstituteService instituteService;
/** The short filter service. */
@Autowired
protected ShortFilterProcessor shortFilterProcessor;
@Autowired
private CaptchaChecker captchaChecker;
/**
* List sub-requests
*
* @return sub-requests
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/r/list", produces = { MediaType.APPLICATION_JSON_VALUE })
public FilteredPage<MaterialSubRequest, MaterialSubRequestFilter> list(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) MaterialSubRequestFilter filter) throws IOException {
ShortFilterService.FilterInfo<MaterialSubRequestFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, MaterialSubRequestFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, requestService.listSubRequests(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.ASC, "id")));
}
/**
* List sub-requests for current user
*
* @return sub-requests
*/
@PreAuthorize("hasRole('REQUESTS')")
@PostMapping(value = "/r/list-mine", produces = { MediaType.APPLICATION_JSON_VALUE })
@JsonView({ JsonViews.Public.class })
public FilteredPage<MaterialSubRequest, MaterialSubRequestFilter> listMine(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) MaterialSubRequestFilter filter) throws IOException {
ShortFilterService.FilterInfo<MaterialSubRequestFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, MaterialSubRequestFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, requestService.listMineSubRequests(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.DESC, "createdDate")));
}
/**
* List requests by filterCode or filter
*
* @param page the page
* @param filterCode short filter code
* @param filter the filter
* @return the page
* @throws IOException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/list", produces = { MediaType.APPLICATION_JSON_VALUE })
@JsonView({ JsonViews.Public.class })
public FilteredPage<MaterialRequest, MaterialRequestFilter> list(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) MaterialRequestFilter filter) throws IOException {
FilterInfo<MaterialRequestFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, MaterialRequestFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, requestService.list(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.ASC, "id")));
}
/**
* Remove request
*
* @return
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/r/{uuid:.{36}}/remove", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest removeRequest(@PathVariable("uuid") UUID uuid) {
LOG.info("Removing request uuid={}", uuid);
return requestService.remove(uuid.toString());
}
/**
* Get request
*
* @return
*/
@RequestMapping(value = "/r/{uuid:.{36}}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest getRequest(@PathVariable("uuid") UUID uuid) {
LOG.info("Loading request uuid={}", uuid);
final MaterialRequest request = requestService.get(uuid.toString());
return request;
}
/**
* Validate request
*
* @return
*/
@PostMapping(value = "/r/{uuid:.{36}}/reconfirm", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest sendValidationEmail(@PathVariable("uuid") UUID uuid) {
LOG.info("Loading request uuid={}", uuid);
final MaterialRequest materialRequest = requestService.get(uuid.toString());
return requestService.sendValidationEmail(materialRequest);
}
/**
* Relay sub-request to holding institute
*
* @return updated sub-request
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/r/{uuid:.{36}}/resend", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialSubRequest relaySubRequest(@PathVariable("uuid") UUID uuid) {
LOG.info("Loading sub-request uuid={}", uuid);
final MaterialSubRequest materialSubRequest = requestService.getSubrequest(uuid.toString());
return requestService.relayRequest(materialSubRequest);
}
/**
* Initiate a new request for material.
*
* @return requests
*/
@PostMapping(value = "/r/initiate", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest initiateRequest(@RequestBody RequestService.RequestData requestData, final HttpServletRequest request) throws RequestService.RequestException, IOException {
if (SecurityContextUtil.getMe() == null) {
// Validate the reCAPTCHA only for anonymous users
captchaChecker.assureValidResponseForClient(requestData.captchaResponse, request.getRemoteAddr());
}
String origin = request.getHeader("Origin");
var languages = request.getHeaders(HttpHeaders.ACCEPT_LANGUAGE);
String lang = languages.hasMoreElements() ? languages.nextElement() : null;
var currentUser = SecurityContextUtil.getCurrentUser();
String sid = currentUser != null ? currentUser.getSid() : null;
LOG.info("Initiating request: origin={} sid={} lang={} info={} accessionUuids={}", origin, sid, lang, requestData.requestInfo, requestData.accessionUuids);
return requestService.initiateRequestByUuids(requestData.requestInfo, requestData.accessionUuids, origin, sid, lang);
}
/**
* Validate request
*
* @return
* @throws EasySMTAException
* @throws NoPidException
*/
@PostMapping(value = "/r/{uuid:.{36}}/validate", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest validateRequest(@PathVariable("uuid") UUID uuid) throws NoPidException, EasySMTAException {
LOG.info("Loading request uuid={}", uuid);
final MaterialRequest materialRequest = requestService.get(uuid.toString());
return requestService.validateRequest(materialRequest);
}
/**
* Validate request
*
* @return
* @throws EasySMTAException
* @throws NoPidException
*/
@PostMapping(value = "/r/validate", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest validateRequest(@RequestParam(PARAM_TOKENUUID) String tokenUuid, @RequestParam(PARAM_KEY) String key) {
LOG.info("Validating request tokenUuid={}, key={}", tokenUuid, key);
try {
return requestService.validateClientRequest(tokenUuid, key);
} catch (NoPidException | EasySMTAException | RequestService.RequestException e) {
throw new InvalidApiUsageException(e.getMessage(), e);
} catch (TokenVerificationService.NoSuchVerificationTokenException e) {
throw new InvalidApiUsageException("Verification token was already used or is not valid!", e);
} catch (TokenVerificationService.TokenExpiredException e) {
throw new InvalidApiUsageException("Verification token is expired!", e);
}
}
/**
* Confirm receipt of request
*
* @return
* @throws InvalidApiUsageException
*/
@PostMapping(value = "/r/confirm", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialSubRequest confirmRequest(
@RequestParam(value = "g-recaptcha-response") final String captchaResponse,
@RequestParam(PARAM_TOKENUUID) String tokenUuid,
@RequestParam(PARAM_KEY) String key,
final HttpServletRequest request) throws IOException {
// Validate the reCAPTCHA
captchaChecker.assureValidResponseForClient(captchaResponse, request.getRemoteAddr());
LOG.info("Validating request tokenUuid={}, key={}", tokenUuid, key);
try {
return requestService.validateReceipt(tokenUuid, key);
} catch (TokenVerificationService.NoSuchVerificationTokenException e) {
throw new InvalidApiUsageException("Verification token was already used or is not valid!", e);
} catch (TokenVerificationService.TokenExpiredException e) {
throw new InvalidApiUsageException("Verification token is expired!", e);
}
}
/**
* Reload PID data
*
* @return
* @throws EasySMTAException
* @throws NoPidException
*/
@PostMapping(value = "/r/{uuid:.{36}}/update-pid", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest recheckPid(@PathVariable("uuid") UUID uuid) throws NoPidException, EasySMTAException {
LOG.info("Loading request uuid={}", uuid);
final MaterialRequest materialRequest = requestService.get(uuid.toString());
return requestService.recheckPid(materialRequest);
}
/**
* List institute requests
*
* @return
*/
@PostMapping(value = "/{instCode}", produces = { MediaType.APPLICATION_JSON_VALUE })
public FilteredPage<MaterialSubRequest, MaterialSubRequestFilter> listInstituteRequests(@PathVariable("instCode") String instCode, @ParameterObject final Pagination page,
@RequestParam(name = "f", required = false) String filterCode,
@RequestBody(required = false) MaterialSubRequestFilter filter) throws IOException {
LOG.info("Listing requests for {}", instCode);
final FaoInstitute institute = instituteService.getInstitute(instCode);
FilterInfo<MaterialSubRequestFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, MaterialSubRequestFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, requestService.list(institute, filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.ASC, "id")));
}
/**
* Get institute request
*
* @return
*/
@GetMapping(value = "/{instCode}/r/{uuid:.{36}}", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialSubRequest getInstituteRequest(@PathVariable("instCode") String instCode, @PathVariable("uuid") UUID uuid) {
LOG.info("Loading request for {} uuid={}", instCode, uuid);
final FaoInstitute institute = instituteService.getInstitute(instCode);
final MaterialSubRequest request = requestService.get(institute, uuid.toString());
return request;
}
@PostMapping(value = "/r/sub/{uuid:.{36}}/provider/info", produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialSubRequest updateProviderInfo(@PathVariable("uuid") UUID uuid, @RequestBody @Valid RequestService.ProviderInfoRequest info) {
return requestService.setProviderInfo(uuid, info);
}
/**
* Get request without confidential information
*
* @return RequestStatusResponse
*/
@RequestMapping(value = "/r/{uuid:.{36}}/status", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public MaterialRequest getRequestStatus(@PathVariable("uuid") UUID uuid) {
LOG.info("Loading request uuid={}", uuid);
return requestService.getRequestStatus(uuid);
}
/**
* List user requests by filterCode or filter
*
* @param page the page
* @param filterCode short filter code
* @param filter the filter
* @return the page
* @throws IOException
*/
@PostMapping(value = "/list-mine", produces = { MediaType.APPLICATION_JSON_VALUE })
@JsonView({ JsonViews.Public.class })
public FilteredPage<MaterialRequest, MaterialRequestFilter> listMyRequests(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
@RequestBody(required = false) MaterialRequestFilter filter) throws IOException {
FilterInfo<MaterialRequestFilter> filterInfo = shortFilterProcessor.processFilter(filterCode, filter, MaterialRequestFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, requestService.listMyRequests(filterInfo.filter, page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, Sort.Direction.ASC, "id")));
}
}