From 385874a6fd067cb17323a27ecd1af62b4176aecc Mon Sep 17 00:00:00 2001 From: Dan Timoney Date: Fri, 26 Jun 2020 15:56:48 -0400 Subject: Add RFC 8040 compliant error handler Add a new error handler that formats error responses consistent with RFC 8040 (RESTCONF) standards. Change-Id: I67a6ab626d943115570f2e74d0a8132933726bc8 Issue-ID: CCSDK-2482 Signed-off-by: Dan Timoney --- .../ccsdk/apps/services/RestApplicationError.java | 31 +++++ .../apps/services/RestApplicationException.java | 14 +++ .../org/onap/ccsdk/apps/services/RestError.java | 57 +++++++++ .../org/onap/ccsdk/apps/services/RestErrors.java | 28 +++++ .../onap/ccsdk/apps/services/RestException.java | 14 +++ .../ccsdk/apps/services/RestExceptionHandler.java | 131 +++++++++++++++++++++ .../ccsdk/apps/services/RestProtocolError.java | 30 +++++ .../ccsdk/apps/services/RestProtocolException.java | 14 +++ .../org/onap/ccsdk/apps/services/RestRpcError.java | 31 +++++ .../onap/ccsdk/apps/services/RestRpcException.java | 14 +++ .../ccsdk/apps/services/RestTransportError.java | 30 +++++ .../apps/services/RestTransportException.java | 14 +++ 12 files changed, 408 insertions(+) create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationError.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationException.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestError.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestErrors.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestException.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestExceptionHandler.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolError.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolException.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestRpcError.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestRpcException.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestTransportError.java create mode 100644 services/src/main/java/org/onap/ccsdk/apps/services/RestTransportException.java (limited to 'services/src/main') diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationError.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationError.java new file mode 100644 index 00000000..e63e1d91 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationError.java @@ -0,0 +1,31 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonValue; + + +@JsonRootName(value="error") +public class RestApplicationError extends RestError { + + + public RestApplicationError() { + this.errorType = "application"; + } + + public RestApplicationError(String errorTag, String errorMessage) { + this.errorType = "application"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = errorMessage; + } + + public RestApplicationError(String errorTag, String errorMessage, Throwable t) { + this.errorType = "application"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = t.getLocalizedMessage(); + } + + +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationException.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationException.java new file mode 100644 index 00000000..73613399 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationException.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.apps.services; + +public class RestApplicationException extends RestException { + + public RestApplicationException(String errorTag, String errorMessage, int status) { + this.restError = new RestApplicationError(errorTag, errorMessage); + this.status = status; + } + + public RestApplicationException(String errorTag, String errorMessage, Throwable t, int status) { + this.restError = new RestApplicationError(errorTag, errorMessage, t); + this.status = status; + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestError.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestError.java new file mode 100644 index 00000000..d2e4c7ab --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestError.java @@ -0,0 +1,57 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +@JsonRootName(value="error") +public abstract class RestError { + + protected String errorType; + protected String errorTag; + protected String errorPath; + protected String errorMessage; + protected String errorInfo; + + + @JsonProperty("error-type") + public String getErrorType() { + return errorType; + } + + @JsonProperty("error-tag") + public String getErrorTag() { + return errorTag; + } + + public void setErrorTag(String errorTag) { + this.errorTag = errorTag; + } + + @JsonProperty("error-path") + public String getErrorPath() { + return errorPath; + } + + public void setErrorPath(String errorPath) { + this.errorPath = errorPath; + } + + @JsonProperty("error-message") + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + @JsonProperty("error-info") + public String getErrorInfo() { + return errorInfo; + } + + public void setErrorInfo(String errorInfo) { + this.errorInfo = errorInfo; + } + +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestErrors.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestErrors.java new file mode 100644 index 00000000..5fac3618 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestErrors.java @@ -0,0 +1,28 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonRootName; + +import java.util.LinkedList; +import java.util.List; + +@JsonRootName(value="errors") +public class RestErrors { + List errors; + + public RestErrors() + { + this.errors = new LinkedList(); + } + + public RestErrors(RestError error) { + this.errors = new LinkedList(); + errors.add(error); + } + public void addError(RestError error) { + errors.add(error); + } + + public List getErrors() { + return errors; + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestException.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestException.java new file mode 100644 index 00000000..52eab71b --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestException.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.apps.services; + +abstract public class RestException extends Exception { + protected RestError restError; + protected int status; + + public int getStatus() { + return status; + } + + public RestError getRestError() { + return restError; + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestExceptionHandler.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestExceptionHandler.java new file mode 100644 index 00000000..c517f402 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestExceptionHandler.java @@ -0,0 +1,131 @@ +package org.onap.ccsdk.apps.services; + +import org.jvnet.hk2.annotations.Service; +import org.springframework.beans.ConversionNotSupportedException; +import org.springframework.beans.TypeMismatchException; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.async.AsyncRequestTimeoutException; +import org.springframework.web.multipart.support.MissingServletRequestPartException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import javax.servlet.http.HttpServletRequest; + +@Order(Ordered.HIGHEST_PRECEDENCE) +@RestControllerAdvice +public class RestExceptionHandler extends ResponseEntityExceptionHandler { + + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-method", "Method not supported", ex), status); + } + + @Override + protected ResponseEntity handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-media-type", "Media type not supported", ex), status); + } + + @Override + protected ResponseEntity handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-media-type", "Media type not acceptable", ex), status); + } + + @Override + protected ResponseEntity handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("missing-path", "Missing path variable", ex), status); + } + + @Override + protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("missing-param", "Missing servlet request parameter", ex), status); + } + + @Override + protected ResponseEntity handleServletRequestBindingException(ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("request-binding", "Servlet binding exception", ex), status); + } + + @Override + protected ResponseEntity handleConversionNotSupported(ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("conversion", "Conversion not supported", ex), status); + } + + @Override + protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("type-mismatch", "Type mismatch", ex), status); + } + + @Override + protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-message", "HTTP message not readable", ex), status); + } + + @Override + protected ResponseEntity handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-message", "HTTP message not writable", ex), status); + } + + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-message", "Method argument not valid", ex), status); + } + + @Override + protected ResponseEntity handleMissingServletRequestPart(MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestProtocolError("bad-message", "Missing servlet request part", ex), status); + } + + @Override + protected ResponseEntity handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("binding-error", "Missing servlet request part", ex), status); + } + + @Override + protected ResponseEntity handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("binding-error", "No handler found", ex), status); + } + + @Override + protected ResponseEntity handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) { + return createResponseEntity(new RestApplicationError("timeout", "Async request timeout", ex), status); + } + + @Override + protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { + return createResponseEntity(new RestApplicationError("internal-error", "Internal error", ex), status); + } + + @ExceptionHandler({RestException.class}) + protected ResponseEntity handleRestException(RestException ex, HttpServletRequest request) { + RestError error = ex.getRestError(); + if (request != null) { + error.setErrorPath(request.getServletPath()); + } + return createResponseEntity(error,HttpStatus.valueOf(ex.getStatus())); + } + + private ResponseEntity createResponseEntity(RestError restError, HttpStatus status) { + + return new ResponseEntity<>(new RestErrors(restError), status); + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolError.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolError.java new file mode 100644 index 00000000..2e927b6a --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolError.java @@ -0,0 +1,30 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonValue; + + +@JsonRootName(value="error") +public class RestProtocolError extends RestError { + + + public RestProtocolError() { + this.errorType = "protocol"; + } + + public RestProtocolError(String errorTag, String errorMessage) { + this.errorType = "protocol"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = errorMessage; + } + + public RestProtocolError(String errorTag, String errorMessage, Throwable t) { + this.errorType = "protocol"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = t.getLocalizedMessage(); + } + +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolException.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolException.java new file mode 100644 index 00000000..3de356bc --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolException.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.apps.services; + +public class RestProtocolException extends RestException { + + public RestProtocolException(String errorTag, String errorMessage, int status) { + this.restError = new RestProtocolError(errorTag, errorMessage); + this.status = status; + } + + public RestProtocolException(String errorTag, String errorMessage, Throwable t, int status) { + this.restError = new RestProtocolError(errorTag, errorMessage, t); + this.status = status; + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcError.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcError.java new file mode 100644 index 00000000..63611d8d --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcError.java @@ -0,0 +1,31 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonValue; + + +@JsonRootName(value="error") +public class RestRpcError extends RestError { + + + public RestRpcError() { + this.errorType = "rpc"; + } + + public RestRpcError(String errorTag, String errorMessage) { + this.errorType = "rpc"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = errorMessage; + } + + public RestRpcError(String errorTag, String errorMessage, Throwable t) { + this.errorType = "rpc"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = t.getLocalizedMessage(); + } + + +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcException.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcException.java new file mode 100644 index 00000000..d69f3eb4 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestRpcException.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.apps.services; + +public class RestRpcException extends RestException { + + public RestRpcException(String errorTag, String errorMessage, int status) { + this.restError = new RestRpcError(errorTag, errorMessage); + this.status = status; + } + + public RestRpcException(String errorTag, String errorMessage, Throwable t, int status) { + this.restError = new RestRpcError(errorTag, errorMessage, t); + this.status = status; + } +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportError.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportError.java new file mode 100644 index 00000000..1fd539a6 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportError.java @@ -0,0 +1,30 @@ +package org.onap.ccsdk.apps.services; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonValue; + + +@JsonRootName(value = "error") +public class RestTransportError extends RestError { + + + public RestTransportError() { + this.errorType = "transport"; + } + + public RestTransportError(String errorTag, String errorMessage) { + this.errorType = "transport"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = errorMessage; + } + + public RestTransportError(String errorTag, String errorMessage, Throwable t) { + this.errorType = "transport"; + this.errorTag = errorTag; + this.errorMessage = errorMessage; + this.errorInfo = t.getLocalizedMessage(); + } + +} diff --git a/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportException.java b/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportException.java new file mode 100644 index 00000000..971ee9b7 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/services/RestTransportException.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.apps.services; + +public class RestTransportException extends RestException { + + public RestTransportException(String errorTag, String errorMessage, int status) { + this.restError = new RestTransportError(errorTag, errorMessage); + this.status = status; + } + + public RestTransportException(String errorTag, String errorMessage, Throwable t, int status) { + this.restError = new RestTransportError(errorTag, errorMessage, t); + this.status = status; + } +} -- cgit