Rename and split rest resources ddi, mgmt and system
Signed-off-by: SirWayne <dennis.melzer@bosch-si.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
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) {// NOSONAR
|
||||
throw new SortParameterUnsupportedDirectionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
|
||||
/**
|
||||
* Exception which is thrown in case an request body is not well formaned and
|
||||
* cannot be parsed.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class MessageNotReadableException extends SpServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new MessageNotReadableException with
|
||||
* {@link SpServerError#SP_REST_BODY_NOT_READABLE} error.
|
||||
*/
|
||||
public MessageNotReadableException() {
|
||||
super(SpServerError.SP_REST_BODY_NOT_READABLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.tomcat.util.http.fileupload.FileUploadBase.FileSizeLimitExceededException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
/**
|
||||
* General controller advice for exception handling.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ResponseExceptionHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResponseExceptionHandler.class);
|
||||
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;
|
||||
|
||||
static {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_NOT_EXISTS, HttpStatus.NOT_FOUND);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_ALRREADY_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_UPLOAD_FAILED_SHA1_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_LOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_STATUS_TO_MANY_ENTRIES, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ATTRIBUTES_TO_MANY_ENTRIES, 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);
|
||||
}
|
||||
|
||||
private static HttpStatus getStatusOrDefault(final SpServerError error) {
|
||||
return ERROR_TO_HTTP_STATUS.getOrDefault(error, DEFAULT_RESPONSE_STATUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* method for handling exception of type SpServerRtException. 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(SpServerRtException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
LOG.debug("Handling exception of request {}", request.getRequestURL());
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
final HttpStatus responseStatus;
|
||||
response.setMessage(ex.getMessage());
|
||||
response.setExceptionClass(ex.getClass().getName());
|
||||
if (ex instanceof SpServerRtException) {
|
||||
responseStatus = getStatusOrDefault(((SpServerRtException) ex).getError());
|
||||
response.setErrorCode(((SpServerRtException) ex).getError().getKey());
|
||||
} else {
|
||||
responseStatus = DEFAULT_RESPONSE_STATUS;
|
||||
}
|
||||
return new ResponseEntity<>(response, responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for handling exception of type HttpMessageNotReadableException
|
||||
* 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(HttpMessageNotReadableException.class)
|
||||
public ResponseEntity<ExceptionInfo> handleHttpMessageNotReadableException(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
response.setErrorCode(SpServerError.SP_REST_BODY_NOT_READABLE.getKey());
|
||||
response.setMessage(SpServerError.SP_REST_BODY_NOT_READABLE.getMessage());
|
||||
response.setExceptionClass(MessageNotReadableException.class.getName());
|
||||
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> handleFileLimitExceededException(final HttpServletRequest request,
|
||||
final Exception ex) {
|
||||
LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
|
||||
|
||||
final ExceptionInfo response = new ExceptionInfo();
|
||||
|
||||
if (searchForCause(ex, FileSizeLimitExceededException.class)) {
|
||||
response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getKey());
|
||||
response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getMessage());
|
||||
response.setExceptionClass(FileSizeLimitExceededException.class.getName());
|
||||
} else {
|
||||
response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getKey());
|
||||
response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getMessage());
|
||||
response.setExceptionClass(MultipartException.class.getName());
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private static boolean searchForCause(final Throwable t, final Class<?> lookFor) {
|
||||
if (t != null && t.getCause() != null) {
|
||||
if (t.getCause().getClass().isAssignableFrom(lookFor)) {
|
||||
return true;
|
||||
} else {
|
||||
return searchForCause(t.getCause(), lookFor);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid sort parameter syntax.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SortParameterSyntaxErrorException extends SpServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid sort parameter direction
|
||||
* name.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SortParameterUnsupportedDirectionException extends SpServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.exception;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
|
||||
/**
|
||||
* Exception used by the REST API in case of invalid field name in the sort
|
||||
* parameter.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SortParameterUnsupportedFieldException extends SpServerRtException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.json.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
/**
|
||||
* A exception model rest representation with JSON annotations for response
|
||||
* bodies in case of RESTful exception occurrence.
|
||||
*
|
||||
*/
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public class ExceptionInfo {
|
||||
|
||||
private String exceptionClass;
|
||||
private String errorCode;
|
||||
private String message;
|
||||
private List<String> parameters;
|
||||
|
||||
/**
|
||||
* @return the exceptionClass
|
||||
*/
|
||||
public String getExceptionClass() {
|
||||
return exceptionClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param exceptionClass
|
||||
* the exceptionClass to set
|
||||
*/
|
||||
public void setExceptionClass(final String exceptionClass) {
|
||||
this.exceptionClass = exceptionClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parameters
|
||||
*/
|
||||
public List<String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parameters
|
||||
* the parameters to set
|
||||
*/
|
||||
public void setParameters(final List<String> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorCode
|
||||
*/
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode
|
||||
* the errorCode to set
|
||||
*/
|
||||
public void setErrorCode(final String errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* the message to set
|
||||
*/
|
||||
public void setMessage(final String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
/**
|
||||
* Byte range for resume download operations.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ByteRange {
|
||||
public static final String MULTIPART_BOUNDARY = "THIS_STRING_SEPARATES_MULTIPART";
|
||||
|
||||
private final long start;
|
||||
private final long end;
|
||||
private final long length;
|
||||
private final long total;
|
||||
|
||||
/**
|
||||
* Construct a byte range.
|
||||
*
|
||||
* @param start
|
||||
* Start of the byte range.
|
||||
* @param end
|
||||
* End of the byte range.
|
||||
* @param total
|
||||
* Total length of the byte source.
|
||||
*/
|
||||
public ByteRange(final long start, final long end, final long total) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
length = end - start + 1;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start
|
||||
*/
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the end
|
||||
*/
|
||||
public long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() { // NOSONAR - as this is generated
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (end ^ end >>> 32);
|
||||
result = prime * result + (int) (length ^ length >>> 32);
|
||||
result = prime * result + (int) (start ^ start >>> 32);
|
||||
result = prime * result + (int) (total ^ total >>> 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) { // NOSONAR - as this is generated
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final ByteRange other = (ByteRange) obj;
|
||||
if (end != other.end) {
|
||||
return false;
|
||||
}
|
||||
if (length != other.length) {
|
||||
return false;
|
||||
}
|
||||
if (start != other.start) {
|
||||
return false;
|
||||
}
|
||||
if (total != other.total) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.exception.SpServerRtException;
|
||||
|
||||
/**
|
||||
* Thrown if artifact content streaming to client failed.
|
||||
*/
|
||||
public final class FileSteamingFailedException extends SpServerRtException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new FileUploadFailedException with
|
||||
* {@link SpServerError#SP_REST_BODY_NOT_READABLE} error.
|
||||
*/
|
||||
public FileSteamingFailedException() {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with Throwable.
|
||||
*
|
||||
* @param cause
|
||||
* for the exception
|
||||
*/
|
||||
public FileSteamingFailedException(final Throwable cause) {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with error string.
|
||||
*
|
||||
* @param message
|
||||
* of the error
|
||||
*/
|
||||
public FileSteamingFailedException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.cache.CacheWriteNotify;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import com.google.common.math.DoubleMath;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Utility class for the Rest Source API.
|
||||
*
|
||||
*/
|
||||
public final class RestResourceConversionHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RestResourceConversionHelper.class);
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
// utility class, private constructor.
|
||||
private RestResourceConversionHelper() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write response without target relation.
|
||||
*
|
||||
* @param artifact
|
||||
* the artifact
|
||||
* @param servletResponse
|
||||
* to be sent back to the requesting client
|
||||
* @param request
|
||||
* from the client
|
||||
* @param file
|
||||
* to be write to the client response
|
||||
*
|
||||
* @return http code
|
||||
*/
|
||||
public static ResponseEntity<Void> writeFileResponse(final LocalArtifact artifact,
|
||||
final HttpServletResponse servletResponse, final HttpServletRequest request, final DbArtifact file) {
|
||||
return writeFileResponse(artifact, servletResponse, request, file, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Write response with target relation and publishes events concerning the
|
||||
* download progress based on given {@link UpdateActionStatus}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The request supports RFC7233 range requests.
|
||||
* </p>
|
||||
*
|
||||
* @param artifact
|
||||
* the artifact
|
||||
* @param response
|
||||
* to be sent back to the requesting client
|
||||
* @param request
|
||||
* from the client
|
||||
* @param file
|
||||
* to be write to the client response
|
||||
* @param cacheWriteNotify
|
||||
* to write progress updates to
|
||||
* @param statusId
|
||||
* of the UpdateActionStatus
|
||||
*
|
||||
* @throws IOException
|
||||
* in case of exceptions
|
||||
*
|
||||
* @return http code
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc7233
|
||||
*/
|
||||
public static ResponseEntity<Void> writeFileResponse(final LocalArtifact artifact,
|
||||
final HttpServletResponse response, final HttpServletRequest request, final DbArtifact file,
|
||||
final CacheWriteNotify cacheWriteNotify, final Long statusId) {
|
||||
|
||||
ResponseEntity<Void> result = null;
|
||||
|
||||
final String etag = artifact.getSha1Hash();
|
||||
final Long lastModified = artifact.getLastModifiedAt() != null ? artifact.getLastModifiedAt()
|
||||
: artifact.getCreatedAt();
|
||||
final long length = file.getSize();
|
||||
|
||||
response.reset();
|
||||
response.setBufferSize(BUFFER_SIZE);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + artifact.getFilename());
|
||||
response.setHeader(HttpHeaders.ETAG, etag);
|
||||
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||
response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified);
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
|
||||
final ByteRange full = new ByteRange(0, length - 1, length);
|
||||
final List<ByteRange> ranges = new ArrayList<>();
|
||||
|
||||
// Validate and process Range and If-Range headers.
|
||||
final String range = request.getHeader("Range");
|
||||
if (range != null) {
|
||||
LOG.debug("range header for filename ({}) is: {}", artifact.getFilename(), range);
|
||||
|
||||
// Range header matches"bytes=n-n,n-n,n-n..."
|
||||
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length);
|
||||
LOG.debug("range header for filename ({}) is not satisfiable: ", artifact.getFilename());
|
||||
return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
}
|
||||
|
||||
// RFC: if the representation is unchanged, send me the part(s) that
|
||||
// I am requesting in
|
||||
// Range; otherwise, send me the entire representation.
|
||||
checkForShortcut(request, etag, lastModified, full, ranges);
|
||||
|
||||
// it seems there are valid ranges
|
||||
result = extractRange(response, length, ranges, range);
|
||||
// return if range extraction turned out to be invalid
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// full request - no range
|
||||
if (ranges.isEmpty() || ranges.get(0).equals(full)) {
|
||||
LOG.debug("filename ({}) results into a full request: ", artifact.getFilename());
|
||||
fullfileRequest(artifact, response, file, cacheWriteNotify, statusId, full);
|
||||
result = new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
// standard range request
|
||||
else if (ranges.size() == 1) {
|
||||
LOG.debug("filename ({}) results into a standard range request: ", artifact.getFilename());
|
||||
standardRangeRequest(artifact, response, file, cacheWriteNotify, statusId, ranges);
|
||||
result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT);
|
||||
}
|
||||
// multipart range request
|
||||
else {
|
||||
LOG.debug("filename ({}) results into a multipart range request: ", artifact.getFilename());
|
||||
multipartRangeRequest(artifact, response, file, cacheWriteNotify, statusId, ranges);
|
||||
result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static void fullfileRequest(final LocalArtifact artifact, final HttpServletResponse response,
|
||||
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId, final ByteRange full) {
|
||||
final ByteRange r = full;
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength()));
|
||||
|
||||
try {
|
||||
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId, r.getStart(),
|
||||
r.getLength());
|
||||
} catch (final IOException e) {
|
||||
LOG.error("fullfileRequest of file ({}) failed!", artifact.getFilename(), e);
|
||||
throw new FileSteamingFailedException(artifact.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
private static ResponseEntity<Void> extractRange(final HttpServletResponse response, final long length,
|
||||
final List<ByteRange> ranges, final String range) {
|
||||
ResponseEntity<Void> result = null;
|
||||
if (ranges.isEmpty()) {
|
||||
for (final String part : range.substring(6).split(",")) {
|
||||
long start = sublong(part, 0, part.indexOf('-'));
|
||||
long end = sublong(part, part.indexOf('-') + 1, part.length());
|
||||
|
||||
if (start == -1) {
|
||||
start = length - end;
|
||||
end = length - 1;
|
||||
} else if (end == -1 || end > length - 1) {
|
||||
end = length - 1;
|
||||
}
|
||||
|
||||
// Check if Range is syntactically valid. If not, then return
|
||||
// 416.
|
||||
if (start > end) {
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length);
|
||||
result = new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add range.
|
||||
ranges.add(new ByteRange(start, end, length));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long sublong(final String value, final int beginIndex, final int endIndex) {
|
||||
final String substring = value.substring(beginIndex, endIndex);
|
||||
return substring.length() > 0 ? Long.parseLong(substring) : -1;
|
||||
}
|
||||
|
||||
private static void checkForShortcut(final HttpServletRequest request, final String etag, final long lastModified,
|
||||
final ByteRange full, final List<ByteRange> ranges) {
|
||||
final String ifRange = request.getHeader(HttpHeaders.IF_RANGE);
|
||||
if (ifRange != null && !ifRange.equals(etag)) {
|
||||
try {
|
||||
final long ifRangeTime = request.getDateHeader(HttpHeaders.IF_RANGE);
|
||||
if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
|
||||
ranges.add(full);
|
||||
}
|
||||
} catch (final IllegalArgumentException ignore) {
|
||||
LOG.info("Invalid if-range header field", ignore);
|
||||
ranges.add(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void multipartRangeRequest(final LocalArtifact artifact, final HttpServletResponse response,
|
||||
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId,
|
||||
final List<ByteRange> ranges) {
|
||||
response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY);
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
|
||||
try {
|
||||
for (final ByteRange r : ranges) {
|
||||
// Add multipart boundary and header fields for every range.
|
||||
response.getOutputStream().println();
|
||||
response.getOutputStream().println("--" + ByteRange.MULTIPART_BOUNDARY);
|
||||
response.getOutputStream()
|
||||
.println("Content-Range: bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
|
||||
|
||||
// Copy single part range of multi part range.
|
||||
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId,
|
||||
r.getStart(), r.getLength());
|
||||
}
|
||||
|
||||
// End with final multipart boundary.
|
||||
response.getOutputStream().println();
|
||||
response.getOutputStream().print("--" + ByteRange.MULTIPART_BOUNDARY + "--");
|
||||
} catch (final IOException e) {
|
||||
LOG.error("multipartRangeRequest of file ({}) failed!", artifact.getFilename(), e);
|
||||
throw new FileSteamingFailedException(artifact.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
private static void standardRangeRequest(final LocalArtifact artifact, final HttpServletResponse response,
|
||||
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId,
|
||||
final List<ByteRange> ranges) {
|
||||
final ByteRange r = ranges.get(0);
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength()));
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
|
||||
try {
|
||||
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId, r.getStart(),
|
||||
r.getLength());
|
||||
} catch (final IOException e) {
|
||||
LOG.error("standardRangeRequest of file ({}) failed!", artifact.getFilename(), e);
|
||||
throw new FileSteamingFailedException(artifact.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
private static long copyStreams(final InputStream from, final OutputStream to,
|
||||
final CacheWriteNotify cacheWriteNotify, final Long statusId, final long start, final long length)
|
||||
throws IOException {
|
||||
checkNotNull(from);
|
||||
checkNotNull(to);
|
||||
final byte[] buf = new byte[BUFFER_SIZE];
|
||||
long total = 0;
|
||||
int progressPercent = 1;
|
||||
|
||||
// skipp until start is reached
|
||||
long skipped = 0;
|
||||
do {
|
||||
skipped += from.skip(start);
|
||||
} while (skipped < start);
|
||||
|
||||
long toRead = length;
|
||||
boolean toContinue = true;
|
||||
|
||||
while (toContinue) {
|
||||
final int r = from.read(buf);
|
||||
if (r == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
toRead -= r;
|
||||
if (toRead > 0) {
|
||||
to.write(buf, 0, r);
|
||||
total += r;
|
||||
} else {
|
||||
to.write(buf, 0, (int) toRead + r);
|
||||
total += toRead + r;
|
||||
toContinue = false;
|
||||
}
|
||||
|
||||
if (cacheWriteNotify != null) {
|
||||
final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, RoundingMode.DOWN);
|
||||
|
||||
// every 10 percent an event
|
||||
if (newPercent == 100 || newPercent > progressPercent + 10) {
|
||||
progressPercent = newPercent;
|
||||
cacheWriteNotify.downloadProgressPercent(statusId, progressPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks given CSV string for defined match value or * wildcard.
|
||||
*
|
||||
* @param matchHeader
|
||||
* to search through
|
||||
* @param toMatch
|
||||
* to search for
|
||||
*
|
||||
* @return <code>true</code> if string matches.
|
||||
*/
|
||||
public static boolean matchesHttpHeader(final String matchHeader, final String toMatch) {
|
||||
final String[] matchValues = matchHeader.split("\\s*,\\s*");
|
||||
Arrays.sort(matchValues);
|
||||
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
||||
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;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* 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 FieldNameProvider}
|
||||
* @param sortString
|
||||
* the string representation of the query parameters. Might be
|
||||
* {@code null} or an empty string.
|
||||
* @return a list which holds the {@link FieldNameProvider} 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> & FieldNameProvider> List<Order> parse(final Class<T> enumType,
|
||||
final String sortString) throws SortParameterSyntaxErrorException {
|
||||
final List<Order> parsedSortings = Lists.newArrayList();
|
||||
// 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.getFieldName()));
|
||||
} 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 FieldNameProvider}
|
||||
* @return the corresponding enum
|
||||
* @throws SortParameterUnsupportedFieldException
|
||||
* if there is no matching enum for the specified name
|
||||
*/
|
||||
private static <T extends Enum<T> & FieldNameProvider> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.json.model;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
||||
import org.junit.Test;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Unit Tests - Management API")
|
||||
@Stories("Error Handling")
|
||||
public class ExceptionInfoTest {
|
||||
|
||||
@Test
|
||||
@Description("Ensures that setters and getters match on teh payload.")
|
||||
public void setterAndGetterOnExceptionInfo() {
|
||||
final String knownExceptionClass = "hawkbit.test.exception.Class";
|
||||
final String knownErrorCode = "hawkbit.error.code.Known";
|
||||
final String knownMessage = "a known message";
|
||||
final List<String> knownParameters = new ArrayList<>();
|
||||
knownParameters.add("param1");
|
||||
knownParameters.add("param2");
|
||||
|
||||
final ExceptionInfo underTest = new ExceptionInfo();
|
||||
underTest.setErrorCode(knownErrorCode);
|
||||
underTest.setExceptionClass(knownExceptionClass);
|
||||
underTest.setMessage(knownMessage);
|
||||
underTest.setParameters(knownParameters);
|
||||
|
||||
assertThat(underTest.getErrorCode()).as("The error code should match with the known error code in the test")
|
||||
.isEqualTo(knownErrorCode);
|
||||
assertThat(underTest.getExceptionClass())
|
||||
.as("The exception class should match with the known error code in the test")
|
||||
.isEqualTo(knownExceptionClass);
|
||||
assertThat(underTest.getMessage()).as("The message should match with the known error code in the test")
|
||||
.isEqualTo(knownMessage);
|
||||
assertThat(underTest.getParameters()).as("The parameters should match with the known error code in the test")
|
||||
.isEqualTo(knownParameters);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.rest.util;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterSyntaxErrorException;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedDirectionException;
|
||||
import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedFieldException;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Features("Component Tests - Management API")
|
||||
@Stories("Sorting parameter")
|
||||
public class SortUtilityTest {
|
||||
private static final String SORT_PARAM_1 = "NAME:ASC";
|
||||
private static final String SORT_PARAM_2 = "NAME:ASC, DESCRIPTION:DESC";
|
||||
private static final String SYNTAX_FAILURE_SORT_PARAM = "NAME:ASC DESCRIPTION:DESC";
|
||||
private static final String WRONG_DIRECTION_PARAM = "NAME:ASDF";
|
||||
private static final String CASE_INSENSITIVE_DIRECTION_PARAM = "NaME:ASC";
|
||||
private static final String CASE_INSENSITIVE_DIRECTION_PARAM_1 = "name:ASC";
|
||||
private static final String WRONG_FIELD_PARAM = "ASDF:ASC";
|
||||
|
||||
@Test
|
||||
@Description("Ascending sorting based on name.")
|
||||
public void parseSortParam1() {
|
||||
final List<Order> parse = SortUtility.parse(TargetFields.class, SORT_PARAM_1);
|
||||
assertThat(1).as("Count of parsing parameter").isEqualTo(parse.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ascending sorting based on name and descending sorting based on description.")
|
||||
public void parseSortParam2() {
|
||||
final List<Order> parse = SortUtility.parse(TargetFields.class, SORT_PARAM_2);
|
||||
assertThat(2).as("Count of parsing parameter").isEqualTo(parse.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Sorting with wrong syntax leads to SortParameterSyntaxErrorException.")
|
||||
public void parseWrongSyntaxParam() {
|
||||
try {
|
||||
SortUtility.parse(TargetFields.class, SYNTAX_FAILURE_SORT_PARAM);
|
||||
fail("SortParameterSyntaxErrorException expected because of wrong syntax");
|
||||
} catch (final SortParameterSyntaxErrorException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Sorting based on name with case sensitive is possible.")
|
||||
public void parsingIsNotCaseSensitive() {
|
||||
SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM);
|
||||
SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Sorting with unknown direction order leads to SortParameterUnsupportedDirectionException.")
|
||||
public void parseWrongDirectionParam() {
|
||||
try {
|
||||
SortUtility.parse(TargetFields.class, WRONG_DIRECTION_PARAM);
|
||||
fail("SortParameterUnsupportedDirectionException expected because of unknown direction order");
|
||||
} catch (final SortParameterUnsupportedDirectionException e) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Sorting with unknown field leads to SortParameterUnsupportedFieldException.")
|
||||
public void parseWrongFieldParam() {
|
||||
try {
|
||||
SortUtility.parse(TargetFields.class, WRONG_FIELD_PARAM);
|
||||
fail("SortParameterUnsupportedFieldException expected because of unknown field");
|
||||
} catch (final SortParameterUnsupportedFieldException e) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
#
|
||||
# All rights reserved. This program and the accompanying materials
|
||||
# are made available under the terms of the Eclipse Public License v1.0
|
||||
# which accompanies this distribution, and is available at
|
||||
# http://www.eclipse.org/legal/epl-v10.html
|
||||
#
|
||||
|
||||
# used if IM profile is disabled
|
||||
security.ignored=true
|
||||
|
||||
# IM required for integration tests
|
||||
spring.profiles.active=im,suiteembedded,artifactrepository,redis
|
||||
|
||||
server.port=12222
|
||||
|
||||
spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value}
|
||||
spring.data.mongodb.port=28017
|
||||
|
||||
|
||||
# supported: H2, MYSQL
|
||||
hawkbit.server.database=H2
|
||||
hawkbit.server.database.env=TEST
|
||||
spring.main.show_banner=false
|
||||
|
||||
hawkbit.server.ddi.security.authentication.header=true
|
||||
|
||||
hawkbit.server.artifact.repo.upload.maxFileSize=5MB
|
||||
|
||||
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
|
||||
|
||||
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
|
||||
|
||||
spring.jpa.database=${hawkbit.server.database}
|
||||
#spring.jpa.show-sql=true
|
||||
|
||||
|
||||
flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
|
||||
|
||||
# effective DB setting
|
||||
spring.datasource.url=${${hawkbit.server.database}.spring.datasource.url}
|
||||
spring.datasource.driverClassName=${${hawkbit.server.database}.spring.datasource.driverClassName}
|
||||
spring.datasource.username=${${hawkbit.server.database}.spring.datasource.username}
|
||||
spring.datasource.password=${${hawkbit.server.database}.spring.datasource.password}
|
||||
|
||||
# H2
|
||||
##;AUTOCOMMIT=ON
|
||||
H2.spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE
|
||||
#H2.spring.datasource.url=jdbc:h2:./db/sp-db;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;MVCC=TRUE
|
||||
H2.spring.datasource.driverClassName=org.h2.Driver
|
||||
H2.spring.datasource.username=sa
|
||||
H2.spring.datasource.password=sa
|
||||
|
||||
# MYSQL
|
||||
MYSQL.spring.datasource.url=jdbc:mysql://localhost:3306/sp_test
|
||||
MYSQL.spring.datasource.driverClassName=org.mariadb.jdbc.Driver
|
||||
MYSQL.spring.datasource.username=root
|
||||
MYSQL.spring.datasource.password=
|
||||
|
||||
# SP Controller configuration
|
||||
hawkbit.controller.pollingTime=00:01:00
|
||||
hawkbit.controller.pollingOverdueTime=00:01:00
|
||||
26
hawkbit-rest-core/src/test/resources/log4j2.xml
Normal file
26
hawkbit-rest-core/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
-->
|
||||
<Configuration status="WARN" monitorInterval="30">
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="[%t] [%-5level] %logger{36} - %msg%n" charset="UTF-8"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.eclipse.hawkbit.MockMvcResultPrinter" level="error" />
|
||||
|
||||
<Root level="error">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user