Refactor rest core (#1955)
* remove unused methods * move mgmt resource dedicated classes to mgmt-resource * clean up code Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -9,15 +9,43 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest;
|
||||
|
||||
import org.eclipse.hawkbit.rest.exception.ResponseExceptionHandler;
|
||||
import org.eclipse.hawkbit.rest.filter.ExcludePathAwareShallowETagFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
|
||||
import org.eclipse.hawkbit.rest.exception.MultiPartFileUploadException;
|
||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||
import org.eclipse.hawkbit.rest.util.FileStreamingFailedException;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.filter.ShallowEtagHeaderFilter;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
/**
|
||||
* Configuration for Rest api.
|
||||
@@ -27,8 +55,9 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
public class RestConfiguration {
|
||||
|
||||
/**
|
||||
* {@link ControllerAdvice} for mapping {@link RuntimeException}s from the
|
||||
* repository to {@link HttpStatus} codes.
|
||||
* {@link ControllerAdvice} for mapping {@link RuntimeException}s from the repository to {@link HttpStatus} codes.
|
||||
*
|
||||
* @return a controller advice for handling exceptions
|
||||
*/
|
||||
@Bean
|
||||
ResponseExceptionHandler responseExceptionHandler() {
|
||||
@@ -38,17 +67,15 @@ public class RestConfiguration {
|
||||
/**
|
||||
* Filter registration bean for spring etag filter.
|
||||
*
|
||||
* @return the spring filter registration bean for registering an etag
|
||||
* filter in the filter chain
|
||||
* @return the spring filter registration bean for registering an etag filter in the filter chain
|
||||
*/
|
||||
@Bean
|
||||
FilterRegistrationBean<ExcludePathAwareShallowETagFilter> eTagFilter() {
|
||||
|
||||
final FilterRegistrationBean<ExcludePathAwareShallowETagFilter> filterRegBean = new FilterRegistrationBean<>();
|
||||
|
||||
// Exclude the URLs for downloading artifacts, so no eTag is generated
|
||||
// in the ShallowEtagHeaderFilter, just using the SH1 hash of the
|
||||
// artifact itself as 'ETag', because otherwise the file will be copied
|
||||
// in memory!
|
||||
// in the ShallowEtagHeaderFilter, just using the SHA1 hash of the
|
||||
// artifact itself as 'ETag', because otherwise the file will be copied in memory!
|
||||
filterRegBean.setFilter(new ExcludePathAwareShallowETagFilter(
|
||||
"/rest/v1/softwaremodules/{smId}/artifacts/{artId}/download",
|
||||
"/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/**",
|
||||
@@ -56,4 +83,254 @@ public class RestConfiguration {
|
||||
|
||||
return filterRegBean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General controller advice for exception handling.
|
||||
*/
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
public static class ResponseExceptionHandler {
|
||||
|
||||
private static final Map<SpServerError, HttpStatus> ERROR_TO_HTTP_STATUS = new EnumMap<>(SpServerError.class);
|
||||
private static final HttpStatus DEFAULT_RESPONSE_STATUS = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
private static final String MESSAGE_FORMATTER_SEPARATOR = " ";
|
||||
|
||||
static {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_NOT_EXISTS, HttpStatus.NOT_FOUND);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_ALREADY_EXISTS, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_READ_ONLY, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_INSUFFICIENT_PERMISSION, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA256_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_DELETE_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_BINARY_DELETED, HttpStatus.GONE);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_LOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_FILE_SIZE_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STORAGE_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_NOT_CANCELABLE, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_NOT_FORCE_QUITABLE, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_CREATION_FAILED_MISSING_MODULE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_MODULE_UNSUPPORTED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_KEY_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONSTRAINT_VIOLATION, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_OPERATION_NOT_SUPPORTED, HttpStatus.GONE);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONCURRENT_MODIFICATION, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_AUTO_CONFIRMATION_ALREADY_ACTIVE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_INCOMPATIBLE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_KEY_OR_NAME_REQUIRED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_LOCKED, HttpStatus.LOCKED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DELETED, HttpStatus.NOT_FOUND);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED);
|
||||
}
|
||||
|
||||
/**
|
||||
* method for handling exception of type AbstractServerRtException. Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information as entity.
|
||||
*/
|
||||
@ExceptionHandler(AbstractServerRtException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
final ExceptionInfo response = createExceptionInfo(ex);
|
||||
final HttpStatus responseStatus;
|
||||
if (ex instanceof AbstractServerRtException) {
|
||||
responseStatus = getStatusOrDefault(((AbstractServerRtException) ex).getError());
|
||||
} else {
|
||||
responseStatus = DEFAULT_RESPONSE_STATUS;
|
||||
}
|
||||
return new ResponseEntity<>(response, responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type {@link FileStreamingFailedException} which is thrown in case the streaming of a file failed
|
||||
* due to an internal server error. As the streaming of the file has already begun, no JSON response but only the ResponseStatus 500
|
||||
* is returned.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the response status 500
|
||||
*/
|
||||
@ExceptionHandler(FileStreamingFailedException.class)
|
||||
public ResponseEntity<Object> handleFileStreamingFailedException(final HttpServletRequest request, final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
log.warn("File streaming failed: {}", ex.getMessage());
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type HttpMessageNotReadableException and MethodArgumentNotValidException which are thrown in case
|
||||
* the request body is not well-formed (e.g. syntax failures, missing/invalid parameters) and cannot be deserialized.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information as entity.
|
||||
*/
|
||||
@ExceptionHandler({
|
||||
HttpMessageNotReadableException.class,
|
||||
MethodArgumentNotValidException.class, HandlerMethodValidationException.class,
|
||||
IllegalArgumentException.class })
|
||||
public ResponseEntity<ExceptionInfo> handleExceptionCausedByIncorrectRequestBody(final HttpServletRequest request, final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
final ExceptionInfo response = createExceptionInfo(new MessageNotReadableException());
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type ConstraintViolationException which is thrown in case the request is rejected due to a
|
||||
* constraint violation.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information as entity.
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleConstraintViolationException(final HttpServletRequest request, final ConstraintViolationException ex) {
|
||||
logRequest(request, ex);
|
||||
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getConstraintViolations().stream()
|
||||
.map(violation -> violation.getPropertyPath() + MESSAGE_FORMATTER_SEPARATOR + violation.getMessage() + ".")
|
||||
.collect(Collectors.joining(MESSAGE_FORMATTER_SEPARATOR)));
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey());
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type ValidationException which is thrown in case the request is rejected due to invalid requests.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information as entity.
|
||||
*/
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleValidationException(final HttpServletRequest request, final ValidationException ex) {
|
||||
logRequest(request, ex);
|
||||
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getMessage());
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey());
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type {@link MultipartException} which is thrown in case the request body is not well-formed and
|
||||
* cannot be deserialized.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information as entity.
|
||||
*/
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleMultipartException(final HttpServletRequest request, final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
|
||||
final List<Throwable> throwables = ExceptionUtils.getThrowableList(ex);
|
||||
final Throwable responseCause = throwables.get(throwables.size() - 1);
|
||||
|
||||
if (ObjectUtils.isEmpty(responseCause.getMessage())) {
|
||||
log.warn("Request {} lead to MultipartException without root cause message:\n{}", request.getRequestURL(),
|
||||
ex.getStackTrace());
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(createExceptionInfo(new MultiPartFileUploadException(responseCause)), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private static HttpStatus getStatusOrDefault(final SpServerError error) {
|
||||
return ERROR_TO_HTTP_STATUS.getOrDefault(error, DEFAULT_RESPONSE_STATUS);
|
||||
}
|
||||
|
||||
private void logRequest(final HttpServletRequest request, final Exception ex) {
|
||||
log.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
|
||||
}
|
||||
|
||||
private ExceptionInfo createExceptionInfo(final Exception ex) {
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getMessage());
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
if (ex instanceof AbstractServerRtException) {
|
||||
response.setErrorCode(((AbstractServerRtException) ex).getError().getKey());
|
||||
response.setInfo(((AbstractServerRtException) ex).getInfo());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link ShallowEtagHeaderFilter} with exclusion paths to exclude some paths
|
||||
* where no ETag header should be generated due that calculating the ETag is an
|
||||
* expensive operation and the response output need to be copied in memory which
|
||||
* should be excluded in case of artifact downloads which could be big of size.
|
||||
*/
|
||||
static class ExcludePathAwareShallowETagFilter extends ShallowEtagHeaderFilter {
|
||||
|
||||
private final String[] excludeAntPaths;
|
||||
private final AntPathMatcher antMatcher = new AntPathMatcher();
|
||||
|
||||
/**
|
||||
* @param excludeAntPaths
|
||||
*/
|
||||
public ExcludePathAwareShallowETagFilter(final String... excludeAntPaths) {
|
||||
this.excludeAntPaths = excludeAntPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
final boolean shouldExclude = shouldExclude(request);
|
||||
if (shouldExclude) {
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
super.doFilterInternal(request, response, filterChain);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldExclude(final HttpServletRequest request) {
|
||||
for (final String pattern : excludeAntPaths) {
|
||||
if (antMatcher.match(request.getContextPath() + pattern, request.getRequestURI())) {
|
||||
// exclude this request from eTag filter
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.data;
|
||||
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedDirectionException;
|
||||
|
||||
/**
|
||||
* A definition of possible sorting direction.
|
||||
*/
|
||||
public enum SortDirection {
|
||||
/**
|
||||
* Ascending.
|
||||
*/
|
||||
ASC,
|
||||
/**
|
||||
* Descending.
|
||||
*/
|
||||
DESC;
|
||||
|
||||
/**
|
||||
* Returns the sort direction for the given name.
|
||||
*
|
||||
* @param name the name of the enum
|
||||
* @return the corresponding enum
|
||||
* @throws SortParameterUnsupportedDirectionException if there is no matching enum for the specified name
|
||||
*/
|
||||
public static SortDirection getByName(final String name) {
|
||||
try {
|
||||
return valueOf(name.toUpperCase());
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
throw new SortParameterUnsupportedDirectionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,20 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Thrown if a multi part exception occurred.
|
||||
* Thrown if a multipart exception occurred.
|
||||
*/
|
||||
public final class MultiPartFileUploadException extends AbstractServerRtException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public MultiPartFileUploadException(final Throwable cause) {
|
||||
super(cause.getMessage(), SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||
import org.eclipse.hawkbit.rest.util.FileStreamingFailedException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
/**
|
||||
* General controller advice for exception handling.
|
||||
*/
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
public class ResponseExceptionHandler {
|
||||
|
||||
private static final Map<SpServerError, HttpStatus> ERROR_TO_HTTP_STATUS = new EnumMap<>(SpServerError.class);
|
||||
private static final HttpStatus DEFAULT_RESPONSE_STATUS = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
private static final String MESSAGE_FORMATTER_SEPARATOR = " ";
|
||||
|
||||
static {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_NOT_EXISTS, HttpStatus.NOT_FOUND);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_ALREADY_EXISTS, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_READ_ONLY, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_INSUFFICIENT_PERMISSION, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA256_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_DELETE_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_BINARY_DELETED, HttpStatus.GONE);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_LOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_FILE_SIZE_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STORAGE_QUOTA_EXCEEDED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_NOT_CANCELABLE, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_NOT_FORCE_QUITABLE, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_CREATION_FAILED_MISSING_MODULE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_MODULE_UNSUPPORTED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_KEY_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONSTRAINT_VIOLATION, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_OPERATION_NOT_SUPPORTED, HttpStatus.GONE);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONCURRENT_MODIFICATION, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_AUTO_CONFIRMATION_ALREADY_ACTIVE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_INCOMPATIBLE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_KEY_OR_NAME_REQUIRED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_LOCKED, HttpStatus.LOCKED);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DELETED, HttpStatus.NOT_FOUND);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED);
|
||||
}
|
||||
|
||||
/**
|
||||
* method for handling exception of type AbstractServerRtException. Called
|
||||
* by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information
|
||||
* as entity.
|
||||
*/
|
||||
@ExceptionHandler(AbstractServerRtException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
final ExceptionInfo response = createExceptionInfo(ex);
|
||||
final HttpStatus responseStatus;
|
||||
if (ex instanceof AbstractServerRtException) {
|
||||
responseStatus = getStatusOrDefault(((AbstractServerRtException) ex).getError());
|
||||
} else {
|
||||
responseStatus = DEFAULT_RESPONSE_STATUS;
|
||||
}
|
||||
return new ResponseEntity<>(response, responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type
|
||||
* {@link FileStreamingFailedException} which is thrown in case the
|
||||
* streaming of a file failed due to an internal server error. As the
|
||||
* streaming of the file has already begun, no JSON response but only the
|
||||
* ResponseStatus 500 is returned. Called by the Spring-Framework for
|
||||
* exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the response status 500
|
||||
*/
|
||||
@ExceptionHandler(FileStreamingFailedException.class)
|
||||
public ResponseEntity<Object> handleFileStreamingFailedException(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
log.warn("File streaming failed: {}", ex.getMessage());
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type HttpMessageNotReadableException and
|
||||
* MethodArgumentNotValidException which are thrown in case the request body
|
||||
* is not well formed (e.g. syntax failures, missing/invalid parameters) and
|
||||
* cannot be deserialized. Called by the Spring-Framework for exception
|
||||
* handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information
|
||||
* as entity.
|
||||
*/
|
||||
@ExceptionHandler({
|
||||
HttpMessageNotReadableException.class,
|
||||
MethodArgumentNotValidException.class, HandlerMethodValidationException.class,
|
||||
IllegalArgumentException.class })
|
||||
public ResponseEntity<ExceptionInfo> handleExceptionCausedByIncorrectRequestBody(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
logRequest(request, ex);
|
||||
final ExceptionInfo response = createExceptionInfo(new MessageNotReadableException());
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type ConstraintViolationException which
|
||||
* is thrown in case the request is rejected due to a constraint violation.
|
||||
* Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information
|
||||
* as entity.
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleConstraintViolationException(final HttpServletRequest request,
|
||||
final ConstraintViolationException ex) {
|
||||
logRequest(request, ex);
|
||||
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getConstraintViolations().stream().map(
|
||||
violation -> violation.getPropertyPath() + MESSAGE_FORMATTER_SEPARATOR + violation.getMessage() + ".")
|
||||
.collect(Collectors.joining(MESSAGE_FORMATTER_SEPARATOR)));
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey());
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type ValidationException which is thrown
|
||||
* in case the request is rejected due to invalid requests. Called by the
|
||||
* Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information
|
||||
* as entity.
|
||||
*/
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleValidationException(final HttpServletRequest request,
|
||||
final ValidationException ex) {
|
||||
logRequest(request, ex);
|
||||
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getMessage());
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey());
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type {@link MultipartException} which is
|
||||
* thrown in case the request body is not well formed and cannot be
|
||||
* deserialized. Called by the Spring-Framework for exception handling.
|
||||
*
|
||||
* @param request the Http request
|
||||
* @param ex the exception which occurred
|
||||
* @return the entity to be responded containing the exception information
|
||||
* as entity.
|
||||
*/
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleMultipartException(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
|
||||
logRequest(request, ex);
|
||||
|
||||
final List<Throwable> throwables = ExceptionUtils.getThrowableList(ex);
|
||||
final Throwable responseCause = throwables.get(throwables.size() - 1);
|
||||
|
||||
if (ObjectUtils.isEmpty(responseCause.getMessage())) {
|
||||
log.warn("Request {} lead to MultipartException without root cause message:\n{}", request.getRequestURL(),
|
||||
ex.getStackTrace());
|
||||
}
|
||||
|
||||
final ExceptionInfo response = createExceptionInfo(new MultiPartFileUploadException(responseCause));
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private static HttpStatus getStatusOrDefault(final SpServerError error) {
|
||||
return ERROR_TO_HTTP_STATUS.getOrDefault(error, DEFAULT_RESPONSE_STATUS);
|
||||
}
|
||||
|
||||
private void logRequest(final HttpServletRequest request, final Exception ex) {
|
||||
log.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
|
||||
}
|
||||
|
||||
private ExceptionInfo createExceptionInfo(final Exception ex) {
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setMessage(ex.getMessage());
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
if (ex instanceof AbstractServerRtException) {
|
||||
response.setErrorCode(((AbstractServerRtException) ex).getError().getKey());
|
||||
response.setInfo(((AbstractServerRtException) ex).getInfo());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid sort parameter syntax.
|
||||
*/
|
||||
public class SortParameterSyntaxErrorException extends AbstractServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new SortParameterSyntaxErrorException with
|
||||
* {@link SpServerError#SP_REST_SORT_PARAM_SYNTAX} error.
|
||||
*/
|
||||
public SortParameterSyntaxErrorException() {
|
||||
super(SpServerError.SP_REST_SORT_PARAM_SYNTAX);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid sort parameter direction
|
||||
* name.
|
||||
*/
|
||||
public class SortParameterUnsupportedDirectionException extends AbstractServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new SortParameterSyntaxErrorException.
|
||||
*/
|
||||
public SortParameterUnsupportedDirectionException() {
|
||||
super(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SortParameterSyntaxErrorException with
|
||||
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_DIRECTION} error.
|
||||
*
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* getCause() method). (A null value is permitted, and indicates
|
||||
* that the cause is nonexistent or unknown.)
|
||||
*/
|
||||
public SortParameterUnsupportedDirectionException(final Throwable cause) {
|
||||
super(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid field name in the sort
|
||||
* parameter.
|
||||
*/
|
||||
public class SortParameterUnsupportedFieldException extends AbstractServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new SortParameterSyntaxErrorException with
|
||||
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_FIELD} error.
|
||||
*/
|
||||
public SortParameterUnsupportedFieldException() {
|
||||
super(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SortParameterSyntaxErrorException with
|
||||
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_FIELD} error.
|
||||
*
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* getCause() method). (A null value is permitted, and indicates
|
||||
* that the cause is nonexistent or unknown.)
|
||||
*/
|
||||
public SortParameterUnsupportedFieldException(final Throwable cause) {
|
||||
super(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.filter.ShallowEtagHeaderFilter;
|
||||
|
||||
/**
|
||||
* An {@link ShallowEtagHeaderFilter} with exclusion paths to exclude some paths
|
||||
* where no ETag header should be generated due that calculating the ETag is an
|
||||
* expensive operation and the response output need to be copied in memory which
|
||||
* should be excluded in case of artifact downloads which could be big of size.
|
||||
*/
|
||||
public class ExcludePathAwareShallowETagFilter extends ShallowEtagHeaderFilter {
|
||||
|
||||
private final String[] excludeAntPaths;
|
||||
private final AntPathMatcher antMatcher = new AntPathMatcher();
|
||||
|
||||
/**
|
||||
* @param excludeAntPaths
|
||||
*/
|
||||
public ExcludePathAwareShallowETagFilter(final String... excludeAntPaths) {
|
||||
this.excludeAntPaths = excludeAntPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
|
||||
final FilterChain filterChain) throws ServletException, IOException {
|
||||
final boolean shouldExclude = shouldExclude(request);
|
||||
if (shouldExclude) {
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
super.doFilterInternal(request, response, filterChain);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldExclude(final HttpServletRequest request) {
|
||||
for (final String pattern : excludeAntPaths) {
|
||||
if (antMatcher.match(request.getContextPath() + pattern, request.getRequestURI())) {
|
||||
// exclude this request from eTag filter
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.data;
|
||||
package org.eclipse.hawkbit.rest.json.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
@@ -17,25 +19,9 @@ import org.eclipse.hawkbit.exception.SpServerError;
|
||||
*/
|
||||
public final class FileStreamingFailedException extends AbstractServerRtException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new FileUploadFailedException with
|
||||
* {@link SpServerError#SP_ARTIFACT_LOAD_FAILED} error.
|
||||
*/
|
||||
public FileStreamingFailedException() {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with Throwable.
|
||||
*
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public FileStreamingFailedException(final Throwable cause) {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with error string.
|
||||
*
|
||||
@@ -54,4 +40,4 @@ public final class FileStreamingFailedException extends AbstractServerRtExceptio
|
||||
public FileStreamingFailedException(final String message, final Throwable cause) {
|
||||
super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
/**
|
||||
* Listener for progress on artifact file streaming.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FileStreamingProgressListener {
|
||||
|
||||
/**
|
||||
* Called multiple times during streaming.
|
||||
*
|
||||
* @param requestedBytes requested bytes of the request
|
||||
* @param shippedBytesSinceLast since the last report
|
||||
* @param shippedBytesOverall during the request
|
||||
*/
|
||||
void progress(long requestedBytes, long shippedBytesSinceLast, long shippedBytesOverall);
|
||||
}
|
||||
@@ -443,4 +443,19 @@ public final class FileStreamingUtil {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for progress on artifact file streaming.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FileStreamingProgressListener {
|
||||
|
||||
/**
|
||||
* Called multiple times during streaming.
|
||||
*
|
||||
* @param requestedBytes requested bytes of the request
|
||||
* @param shippedBytesSinceLast since the last report
|
||||
* @param shippedBytesOverall during the request
|
||||
*/
|
||||
void progress(long requestedBytes, long shippedBytesSinceLast, long shippedBytesOverall);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,15 @@ package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Utility class for the Rest Source API.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class HttpUtil {
|
||||
|
||||
private HttpUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks given CSV string for defined match value or wildcard.
|
||||
*
|
||||
@@ -32,5 +32,4 @@ public final class HttpUtil {
|
||||
Arrays.sort(matchValues);
|
||||
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterSyntaxErrorException;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedDirectionException;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedFieldException;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
|
||||
/**
|
||||
* A utility class for parsing query parameters which define the sorting of
|
||||
* elements.
|
||||
*/
|
||||
public final class SortUtility {
|
||||
|
||||
/**
|
||||
* the delimiter between the field and direction in the sort request.
|
||||
*/
|
||||
public static final String DELIMITER_FIELD_DIRECTION = ":";
|
||||
|
||||
private static final String DELIMITER_SORT_TUPLE = ",";
|
||||
|
||||
/*
|
||||
* utility constructor private.
|
||||
*/
|
||||
private SortUtility() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sort string e.g. given in a REST call based on the definition
|
||||
* of sorting: http://localhost/entity?s=field1:ASC, field2:DESC The fields
|
||||
* will be split into the keys of the returned map. The direction of the
|
||||
* sorting will be mapped into the {@link Direction} enum.
|
||||
*
|
||||
* @param enumType the class of the enum which the fields in the sort string
|
||||
* should be related to.
|
||||
* @param <T> the type of the enumeration which must be derived from
|
||||
* {@link RsqlQueryField}
|
||||
* @param sortString the string representation of the query parameters. Might be
|
||||
* {@code null} or an empty string.
|
||||
* @return a list which holds the {@link RsqlQueryField} and the specific
|
||||
* {@link Direction} for them as a tuple. Never {@code null}. In
|
||||
* case of no sorting parameters an empty map will be returned.
|
||||
* @throws SortParameterSyntaxErrorException if the sorting query parameter is not well-formed
|
||||
* @throws SortParameterUnsupportedFieldException if a field name cannot be mapped to the enum type
|
||||
* @throws SortParameterUnsupportedDirectionException if the given direction is not "ASC" or "DESC"
|
||||
*/
|
||||
public static <T extends Enum<T> & RsqlQueryField> List<Order> parse(final Class<T> enumType,
|
||||
final String sortString) throws SortParameterSyntaxErrorException {
|
||||
final List<Order> parsedSortings = new ArrayList<>();
|
||||
// scan the sort tuples e.g. field:direction
|
||||
if (sortString != null) {
|
||||
final StringTokenizer tupleTokenizer = new StringTokenizer(sortString, DELIMITER_SORT_TUPLE);
|
||||
while (tupleTokenizer.hasMoreTokens()) {
|
||||
final String sortTuple = tupleTokenizer.nextToken().trim();
|
||||
final StringTokenizer fieldDirectionTokenizer = new StringTokenizer(sortTuple,
|
||||
DELIMITER_FIELD_DIRECTION);
|
||||
if (fieldDirectionTokenizer.countTokens() == 2) {
|
||||
final String fieldName = fieldDirectionTokenizer.nextToken().trim().toUpperCase();
|
||||
final String sortDirectionStr = fieldDirectionTokenizer.nextToken().trim();
|
||||
|
||||
final T identifier = getAttributeIdentifierByName(enumType, fieldName);
|
||||
|
||||
final Direction sortDirection = getDirection(sortDirectionStr);
|
||||
parsedSortings.add(new Order(sortDirection, identifier.getJpaEntityFieldName()));
|
||||
} else {
|
||||
throw new SortParameterSyntaxErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsedSortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute identifier for the given name.
|
||||
*
|
||||
* @param enumType the class of the enum which the fields in the sort string
|
||||
* should be related to.
|
||||
* @param name the name of the enum
|
||||
* @param <T> the type of the enumeration which must be derived from
|
||||
* {@link RsqlQueryField}
|
||||
* @return the corresponding enum
|
||||
* @throws SortParameterUnsupportedFieldException if there is no matching enum for the specified name
|
||||
*/
|
||||
private static <T extends Enum<T> & RsqlQueryField> T getAttributeIdentifierByName(final Class<T> enumType,
|
||||
final String name) {
|
||||
try {
|
||||
return Enum.valueOf(enumType, name.toUpperCase());
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new SortParameterUnsupportedFieldException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Direction getDirection(final String sortDirectionStr) {
|
||||
try {
|
||||
return Direction.fromString(sortDirectionStr);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new SortParameterUnsupportedDirectionException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user