Add RFC 8040 compliant error handler 15/109615/2
authorDan Timoney <dtimoney@att.com>
Fri, 26 Jun 2020 19:56:48 +0000 (15:56 -0400)
committerDan Timoney <dtimoney@att.com>
Mon, 29 Jun 2020 13:01:18 +0000 (09:01 -0400)
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 <dtimoney@att.com>
23 files changed:
ms/sliboot/pom.xml
ms/sliboot/src/main/dc/docker-compose.yaml
ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/App.java
ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/WebConfig.java
ms/sliboot/src/main/java/org/onap/ccsdk/apps/ms/sliboot/controllers/RestconfApiController.java
ms/sliboot/src/main/resources/startSliboot.sh
ms/sliboot/src/main/templates/api.mustache [new file with mode: 0644]
ms/sliboot/src/test/java/org/onap/ccsdk/apps/ms/sliboot/RestconfApiControllerTest.java
services/pom.xml
services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationError.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestApplicationException.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestError.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestErrors.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestException.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestExceptionHandler.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolError.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestProtocolException.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestRpcError.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestRpcException.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestTransportError.java [new file with mode: 0644]
services/src/main/java/org/onap/ccsdk/apps/services/RestTransportException.java [new file with mode: 0644]
services/src/test/java/org/onap/ccsdk/apps/services/RestExceptionHandlerTest.java [new file with mode: 0644]
services/src/test/resources/log4j2-test.xml [new file with mode: 0644]

index 2d15080..213f02c 100644 (file)
                             <generateApiTests>true</generateApiTests>
                             <ignoreFileOverride>${project.basedir}/.swagger-codegen-ignore</ignoreFileOverride>
                             <withXml>true</withXml>
+                            <templateDirectory>${project.basedir}/src/main/templates</templateDirectory>
                             <configOptions>
                                 <java8>true</java8>
                                 <springBootVersion>2.2.4-RELEASE</springBootVersion>
index f48af3a..b1f2529 100755 (executable)
@@ -2,13 +2,16 @@ version: '2.1'
 
 services:
   db:
-    image: mariadb:10.3
+    image: mariadb:10.5
     container_name: sliboot_db_container
     ports:
       - "13306:3306"
     environment:
       - MYSQL_ROOT_PASSWORD=openECOMP1.0
       - MYSQL_ROOT_HOST=%
+      - MYSQL_USER=sli
+      - MYSQL_PASSWORD=abc123
+      - MYSQL_DATABASE=sdnctl
     logging:       
       driver:   "json-file"
       options:  
@@ -28,7 +31,6 @@ services:
       - db:dbhost
     environment:
       - MYSQL_DB_HOST=dbhost
-      - MYSQL_ROOT_PASSWORD=openECOMP1.0
       - MYSQL_DB_USER=sli
       - MYSQL_DB_PASSWD=abc123
       - MYSQL_DB_DATABASE=sdnctl
index d2ef5e7..b93c2f9 100644 (file)
@@ -35,8 +35,7 @@ import org.onap.aaf.cadi.shiro.AAFRealm;
 \r
 @SpringBootApplication\r
 @EnableSwagger2\r
-@ComponentScan(basePackages = { "org.onap.ccsdk.apps.ms.sliboot.*" })\r
-\r
+@ComponentScan(basePackages = { "org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services" })\r
 public class App {\r
 \r
   private static final Logger log = LoggerFactory.getLogger(App.class);\r
index 3b0b2a2..1cef316 100644 (file)
@@ -28,10 +28,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;\r
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\r
 \r
-@EnableWebMvc\r
 @Configuration\r
 @EnableJpaRepositories("org.onap.ccsdk.apps.ms.sliboot.*")\r
-@ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*"})\r
+@ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services"})\r
 @EntityScan("org.onap.ccsdk.apps.ms.sliboot.*")\r
 @EnableTransactionManagement\r
 public class WebConfig implements WebMvcConfigurer {\r
index 4b78b2d..f37fe13 100644 (file)
@@ -29,6 +29,9 @@ import org.onap.ccsdk.apps.ms.sliboot.swagger.OperationalApi;
 import org.onap.ccsdk.apps.ms.sliboot.swagger.OperationsApi;
 import org.onap.ccsdk.apps.ms.sliboot.data.TestResultsOperationalRepository;
 import org.onap.ccsdk.apps.ms.sliboot.swagger.model.*;
+import org.onap.ccsdk.apps.services.RestApplicationException;
+import org.onap.ccsdk.apps.services.RestException;
+import org.onap.ccsdk.apps.services.RestProtocolException;
 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
 import org.onap.ccsdk.sli.core.sli.provider.base.SvcLogicServiceBase;
@@ -42,6 +45,7 @@ import org.springframework.context.annotation.ComponentScan;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
@@ -49,7 +53,7 @@ import java.util.*;
 
 @javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2020-02-20T12:50:11.207-05:00")
 
-@Controller
+@RestController
 @ComponentScan(basePackages = {"org.onap.ccsdk.apps.ms.sliboot.*", "org.onap.ccsdk.apps.services"})
 @EntityScan("org.onap.ccsdk.apps.ms.sliboot.*")
 public class RestconfApiController implements ConfigApi, OperationalApi, OperationsApi {
@@ -273,7 +277,7 @@ public class RestconfApiController implements ConfigApi, OperationalApi, Operati
        }
 
        @Override
-       public ResponseEntity<SliApiTestResults> configSLIAPItestResultsGet() {
+       public ResponseEntity<SliApiTestResults> configSLIAPItestResultsGet() throws RestException {
 
                if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
                } else {
@@ -282,6 +286,10 @@ public class RestconfApiController implements ConfigApi, OperationalApi, Operati
 
                SliApiTestResults results = new SliApiTestResults();
 
+               if (testResultsConfigRepository.count() == 0) {
+                       throw new RestApplicationException("data-missing", "Request could not be completed because the relevant data model content does not exist", 404);
+               }
+
                testResultsConfigRepository.findAll().forEach(testResult -> {
                        SliApiTestresultsTestResult item = null;
                        try {
index 3ca2ad6..a4cbd25 100644 (file)
@@ -42,17 +42,17 @@ echo -e "\nDatabase ready"
 
 # Create tablespace and user account
 
-mysql -h ${MYSQL_DB_HOST} -u root -p${MYSQL_ROOT_PASSWORD} mysql <<-END
-CREATE DATABASE ${MYSQL_DB_DATABASE};
-CREATE USER '${MYSQL_DB_USER}'@'localhost' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
-CREATE USER '${MYSQL_DB_USER}'@'%' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
-GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'localhost' WITH GRANT OPTION;
-GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'%' WITH GRANT OPTION;
-commit;
-END
+#mysql -h ${MYSQL_DB_HOST} -u root -p${MYSQL_ROOT_PASSWORD} mysql <<-END
+#CREATE DATABASE ${MYSQL_DB_DATABASE} IF NOT EXISTS;
+#CREATE USER '${MYSQL_DB_USER}'@'localhost' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
+#CREATE USER '${MYSQL_DB_USER}'@'%' IDENTIFIED BY '${MYSQL_DB_PASSWD}';
+#GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'localhost' WITH GRANT OPTION;
+#GRANT ALL PRIVILEGES ON ${MYSQL_DB_DATABASE}.* TO '${MYSQL_DB_USER}'@'%' WITH GRANT OPTION;
+#commit;
+#END
 
 # Initialize schema
-mysql -h ${MYSQL_DB_HOST} -u ${MYSQL_DB_USER} -p${MYSQL_DB_PASSWD} ${MYSQL_DB_DATABASE} < ${CCSDK_HOME}/config/schema.sql
+mysql -h ${MYSQL_DB_HOST} -u ${MYSQL_DB_USER} -p${MYSQL_DB_PASSWD} ${MYSQL_DB_DATABASE} < ${CCSDK_HOME}/config/schema.sql
 
 
 echo -e "\nCerts ready"
diff --git a/ms/sliboot/src/main/templates/api.mustache b/ms/sliboot/src/main/templates/api.mustache
new file mode 100644 (file)
index 0000000..c28642c
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+* NOTE: This class is auto generated by the swagger code generator program ({{{generatorVersion}}}).
+* https://github.com/swagger-api/swagger-codegen
+* Do not edit the class manually.
+*/
+package {{package}};
+
+{{#imports}}import {{import}};
+{{/imports}}
+{{#jdk8-no-delegate}}
+    import com.fasterxml.jackson.databind.ObjectMapper;
+{{/jdk8-no-delegate}}
+import io.swagger.annotations.*;
+{{#jdk8-no-delegate}}
+    import org.slf4j.Logger;
+    import org.slf4j.LoggerFactory;
+    import org.springframework.http.HttpStatus;
+{{/jdk8-no-delegate}}
+import org.springframework.http.ResponseEntity;
+{{#useBeanValidation}}
+    import org.springframework.validation.annotation.Validated;
+{{/useBeanValidation}}
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+import org.onap.ccsdk.apps.services.RestException;
+
+{{#jdk8-no-delegate}}
+    import javax.servlet.http.HttpServletRequest;
+{{/jdk8-no-delegate}}
+{{#useBeanValidation}}
+    import javax.validation.Valid;
+    import javax.validation.constraints.*;
+{{/useBeanValidation}}
+{{#jdk8-no-delegate}}
+    import java.io.IOException;
+{{/jdk8-no-delegate}}
+import java.util.List;
+{{#jdk8-no-delegate}}
+    import java.util.Optional;
+{{/jdk8-no-delegate}}
+{{^jdk8-no-delegate}}
+    {{#useOptional}}
+        import java.util.Optional;
+    {{/useOptional}}
+{{/jdk8-no-delegate}}
+{{#async}}
+    import java.util.concurrent.{{^jdk8}}Callable{{/jdk8}}{{#jdk8}}CompletableFuture{{/jdk8}};
+{{/async}}
+{{>generatedAnnotation}}
+@Api(value = "{{{baseName}}}", description = "the {{{baseName}}} API")
+{{#operations}}
+    public interface {{classname}} {
+    {{#jdk8}}
+
+        {{^isDelegate}}
+            Logger log = LoggerFactory.getLogger({{classname}}.class);
+
+            default Optional<ObjectMapper> getObjectMapper() {
+                return Optional.empty();
+                }
+
+                default Optional<HttpServletRequest> getRequest() {
+                return Optional.empty();
+                }
+
+                default Optional<String> getAcceptHeader() {
+                return getRequest().map(r -> r.getHeader("Accept"));
+                }
+        {{/isDelegate}}
+        {{#isDelegate}}
+            {{classname}}Delegate getDelegate();
+        {{/isDelegate}}
+    {{/jdk8}}
+    {{#operation}}
+
+            @ApiOperation(value = "{{{summary}}}", nickname = "{{{operationId}}}", notes = "{{{notes}}}"{{#returnBaseType}}, response = {{{returnBaseType}}}.class{{/returnBaseType}}{{#returnContainer}}, responseContainer = "{{{returnContainer}}}"{{/returnContainer}}{{#hasAuthMethods}}, authorizations = {
+        {{#authMethods}}@Authorization(value = "{{name}}"{{#isOAuth}}, scopes = {
+        {{#scopes}}@AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{#hasMore}},
+        {{/hasMore}}{{/scopes}}
+            }{{/isOAuth}}){{#hasMore}},
+        {{/hasMore}}{{/authMethods}}
+            }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}",{{/vendorExtensions.x-tags}} })
+            @ApiResponses(value = { {{#responses}}
+                @ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{baseType}}}.class{{/baseType}}{{#containerType}}, responseContainer = "{{{containerType}}}"{{/containerType}}){{#hasMore}},{{/hasMore}}{{/responses}} })
+        {{#implicitHeaders}}
+                @ApiImplicitParams({
+            {{#headerParams}}
+                {{>implicitHeader}}
+            {{/headerParams}}
+                })
+        {{/implicitHeaders}}
+            @RequestMapping(value = "{{{path}}}",{{#singleContentTypes}}
+                produces = "{{{vendorExtensions.x-accepts}}}",
+                consumes = "{{{vendorExtensions.x-contentType}}}",{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}
+                produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}}
+                consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} },{{/hasConsumes}}{{/singleContentTypes}}
+            method = RequestMethod.{{httpMethod}})
+        {{#jdk8}}default {{/jdk8}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}{{/allParams}}){{^jdk8}};{{/jdk8}}{{#jdk8}} throws RestException {
+        {{#delegate-method}}
+                return {{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
+                }
+
+                // Override this method
+                default {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#allParams}}{{^isFile}}{{{dataType}}}{{/isFile}}{{#isFile}}MultipartFile{{/isFile}} {{paramName}}{{#hasMore}},{{/hasMore}}{{/allParams}}) throws RestException {
+        {{/delegate-method}}
+        {{^isDelegate}}
+                if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) {
+            {{#examples}}
+                    if (getAcceptHeader().get().contains("{{{contentType}}}")) {
+                    try {
+                    return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(getObjectMapper().get().readValue("{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{>exampleReturnTypes}}.class), HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
+                    } catch (IOException e) {
+                    log.error("Couldn't serialize response for content type {{{contentType}}}", e);
+                    return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR){{#async}}){{/async}};
+                    }
+                    }
+            {{/examples}}
+                } else {
+                log.warn("ObjectMapper or HttpServletRequest not configured in default {{classname}} interface so no example is generated");
+                }
+                return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
+        {{/isDelegate}}
+        {{#isDelegate}}
+                return getDelegate().{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
+        {{/isDelegate}}
+            }{{/jdk8}}
+
+    {{/operation}}
+        }
+{{/operations}}
\ No newline at end of file
index 4a7a7ec..7d38088 100644 (file)
@@ -123,14 +123,15 @@ public class RestconfApiControllerTest {
   public void testTestResultAdd() throws Exception {
     String url = "/config/SLI-API:test-results/";
 
-    MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
-
-    assertEquals(200, mvcResult.getResponse().getStatus());
-
     // Delete any existing content before testing insert
-    mvcResult = mvc.perform(MockMvcRequestBuilders.delete(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
+    MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.delete(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
     assertEquals(200, mvcResult.getResponse().getStatus());
 
+    mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+
+    assertEquals(404, mvcResult.getResponse().getStatus());
+    log.info("Empty test-results returns error - {}", mvcResult.getResponse().getContentAsString());
+
     String jsonString = "{\n" +
             "  \"test-result\" : [\n" +
             "        {\n" +
@@ -146,6 +147,7 @@ public class RestconfApiControllerTest {
     mvcResult = mvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON)).andReturn();
     assertEquals(200, mvcResult.getResponse().getStatus());
 
+
     ObjectMapper objectMapper = new ObjectMapper();
     SliApiTestResults testResults = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), SliApiTestResults.class);
     assertNotNull(testResults);
index d7e60f8..004e55e 100644 (file)
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>javax.ws.rs</groupId>
             <artifactId>javax.ws.rs-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.onap.ccsdk.sli.core</groupId>
             <artifactId>sliPluginUtils-provider</artifactId>
             <groupId>org.glassfish.jersey.inject</groupId>
             <artifactId>jersey-hk2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
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 (file)
index 0000000..e63e1d9
--- /dev/null
@@ -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 (file)
index 0000000..7361339
--- /dev/null
@@ -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 (file)
index 0000000..d2e4c7a
--- /dev/null
@@ -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 (file)
index 0000000..5fac361
--- /dev/null
@@ -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<RestError> errors;
+
+    public RestErrors()
+    {
+        this.errors = new LinkedList<RestError>();
+    }
+
+    public RestErrors(RestError error) {
+        this.errors = new LinkedList<RestError>();
+        errors.add(error);
+    }
+    public void addError(RestError error) {
+        errors.add(error);
+    }
+
+    public List<RestError> 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 (file)
index 0000000..52eab71
--- /dev/null
@@ -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 (file)
index 0000000..c517f40
--- /dev/null
@@ -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<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+      return createResponseEntity(new RestProtocolError("bad-method", "Method not supported", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> 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<Object> 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<Object> handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestApplicationError("missing-path", "Missing path variable", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("missing-param", "Missing servlet request parameter", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestApplicationError("request-binding", "Servlet binding exception", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestApplicationError("conversion", "Conversion not supported", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("type-mismatch", "Type mismatch", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("bad-message", "HTTP message not readable", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("bad-message", "HTTP message not writable", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("bad-message", "Method argument not valid", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestProtocolError("bad-message", "Missing servlet request part", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestApplicationError("binding-error", "Missing servlet request part", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+        return createResponseEntity(new RestApplicationError("binding-error", "No handler found", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) {
+        return createResponseEntity(new RestApplicationError("timeout", "Async request timeout", ex), status);
+    }
+
+    @Override
+    protected ResponseEntity<Object> 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<Object> 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<Object> 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 (file)
index 0000000..2e927b6
--- /dev/null
@@ -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 (file)
index 0000000..3de356b
--- /dev/null
@@ -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 (file)
index 0000000..63611d8
--- /dev/null
@@ -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 (file)
index 0000000..d69f3eb
--- /dev/null
@@ -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 (file)
index 0000000..1fd539a
--- /dev/null
@@ -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 (file)
index 0000000..971ee9b
--- /dev/null
@@ -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;
+    }
+}
diff --git a/services/src/test/java/org/onap/ccsdk/apps/services/RestExceptionHandlerTest.java b/services/src/test/java/org/onap/ccsdk/apps/services/RestExceptionHandlerTest.java
new file mode 100644 (file)
index 0000000..242a601
--- /dev/null
@@ -0,0 +1,362 @@
+package org.onap.ccsdk.apps.services;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.ConversionNotSupportedException;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+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.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 java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+
+public class RestExceptionHandlerTest {
+    private static final Logger log = LoggerFactory.getLogger("RestExceptionHandler");
+    private class RestExceptionHandlerWrapper extends RestExceptionHandler {
+        @Override
+        public  ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleHttpRequestMethodNotSupported(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleHttpMediaTypeNotSupported(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleHttpMediaTypeNotAcceptable(ex, headers, status, request);
+        }
+
+        @Override
+        protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleMissingPathVariable(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleMissingServletRequestParameter(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleServletRequestBindingException(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleConversionNotSupported(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleTypeMismatch(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleHttpMessageNotReadable(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleHttpMessageNotWritable(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleMethodArgumentNotValid(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleMissingServletRequestPart(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleBindException(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleNoHandlerFoundException(ex, headers, status, request);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) {
+            return super.handleAsyncRequestTimeoutException(ex, headers, status, webRequest);
+        }
+
+        @Override
+        public ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
+            return super.handleExceptionInternal(ex, body, headers, status, request);
+        }
+    }
+
+    RestExceptionHandler handler;
+
+    @Before
+    public void setUp() {
+        handler = new RestExceptionHandlerWrapper();
+    }
+
+    @Test
+    public void handleHttpRequestMethodNotSupported() throws JsonProcessingException {
+        String[] supportedMethods = {"GET", "POST", "PUT", "DELETE"};
+
+
+        ResponseEntity<Object> respEntity = handler.handleHttpRequestMethodNotSupported(new HttpRequestMethodNotSupportedException("PATCH", supportedMethods),
+                null, HttpStatus.METHOD_NOT_ALLOWED, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+
+
+    }
+
+    @Test
+    public void handleHttpMediaTypeNotSupported() throws JsonProcessingException {
+        MediaType[] supportedMediaTypeArray = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML};
+
+        ResponseEntity<Object> respEntity = handler.handleHttpMediaTypeNotSupported(new HttpMediaTypeNotSupportedException(MediaType.MULTIPART_MIXED, Arrays.asList(supportedMediaTypeArray)),
+                null, HttpStatus.UNSUPPORTED_MEDIA_TYPE, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleHttpMediaTypeNotAcceptable() throws JsonProcessingException {
+        MediaType[] supportedMediaTypeArray = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML};
+
+        ResponseEntity<Object> respEntity = handler.handleHttpMediaTypeNotAcceptable(new HttpMediaTypeNotAcceptableException(Arrays.asList(supportedMediaTypeArray)),
+                null, HttpStatus.UNSUPPORTED_MEDIA_TYPE, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+
+    }
+
+    @Test
+    public void handleMissingPathVariable() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleMissingPathVariable(new MissingPathVariableException("test", new MethodParameter(RestApplicationError.class.getDeclaredConstructors()[0],-1)),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleMissingServletRequestParameter() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleMissingServletRequestParameter(new MissingServletRequestParameterException("test", "string"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleServletRequestBindingException() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleServletRequestBindingException(new ServletRequestBindingException("servlet request binding error"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleConversionNotSupported() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleConversionNotSupported(new ConversionNotSupportedException("hello", Integer.class, new NumberFormatException()),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleTypeMismatch() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleTypeMismatch(new TypeMismatchException("hello", Integer.class, new NumberFormatException()),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleHttpMessageNotReadable() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleHttpMessageNotReadable(new HttpMessageNotReadableException("Message not readable"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleHttpMessageNotWritable() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleHttpMessageNotWritable(new HttpMessageNotWritableException("Message not writable"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleMethodArgumentNotValid() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleMethodArgumentNotValid(new MethodArgumentNotValidException(new MethodParameter(RestApplicationError.class.getDeclaredConstructors()[0],-1),
+                        new BindException("target", "objectName")),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleMissingServletRequestPart() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleMissingServletRequestPart(new MissingServletRequestPartException("test"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleBindException() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleBindException(new BindException("target", "objectName"),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleNoHandlerFoundException() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleNoHandlerFoundException(new NoHandlerFoundException("GET", "restconf/bogus", null),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleAsyncRequestTimeoutException() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleAsyncRequestTimeoutException(new AsyncRequestTimeoutException(),
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleExceptionInternal() throws JsonProcessingException {
+        ResponseEntity<Object> respEntity = handler.handleExceptionInternal(new NullPointerException(), null,
+                null, HttpStatus.INTERNAL_SERVER_ERROR, null);
+
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+    @Test
+    public void handleRestException() throws JsonProcessingException {
+
+        ResponseEntity<Object> respEntity = handler.handleRestException(new RestApplicationException("no-data-found", "No data found", HttpStatus.NOT_FOUND.value()), null);
+        assertTrue(respEntity.hasBody());
+        assertTrue(respEntity.getBody() instanceof RestErrors);
+
+        RestErrors restErrors = (RestErrors) respEntity.getBody();
+        ObjectMapper objectMapper = new ObjectMapper();
+        log.info("response : {}", objectMapper.writeValueAsString(restErrors));
+    }
+
+}
\ No newline at end of file
diff --git a/services/src/test/resources/log4j2-test.xml b/services/src/test/resources/log4j2-test.xml
new file mode 100644 (file)
index 0000000..1408cf2
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout
+                    pattern="[%p] %d{yyyy-MM-dd HH:mm:ss} %c{1}:%L - %m%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+        </Root>
+    </Loggers>
+</Configuration>
\ No newline at end of file