Rename and split rest resources ddi, mgmt and system

Signed-off-by: SirWayne <dennis.melzer@bosch-si.com>
This commit is contained in:
SirWayne
2016-04-20 17:33:03 +02:00
parent 8a22ea3df3
commit c3c405c986
169 changed files with 3081 additions and 1871 deletions

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}
}

View File

@@ -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

View 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>