Merge "Spring Boot Actuator enabled on /manage" into cps_poc
authorToine Siebelink <toine.siebelink@est.tech>
Fri, 6 Nov 2020 16:45:55 +0000 (16:45 +0000)
committerNordix Gerrit <gerrit@nordix.org>
Fri, 6 Nov 2020 16:45:55 +0000 (16:45 +0000)
16 files changed:
cps/README.md
cps/cps-dependencies/pom.xml
cps/cps-parent/pom.xml
cps/cps-rest/docs/api/swagger/openapi.yml
cps/cps-rest/pom.xml
cps/cps-rest/src/main/java/org/onap/cps/rest/controller/CpsRestController.java
cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java [new file with mode: 0644]
cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/ErrorMessage.java [new file with mode: 0644]
cps/cps-rest/src/main/java/org/onap/cps/swagger/config/SpringFoxConfig.java [new file with mode: 0644]
cps/cps-rest/src/main/resources/application.yml
cps/cps-service/src/main/java/org/onap/cps/api/CpService.java
cps/cps-service/src/main/java/org/onap/cps/api/impl/CpServiceImpl.java
cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java [new file with mode: 0644]
cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java [new file with mode: 0644]
cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java [new file with mode: 0644]
cps/cps-service/src/test/groovy/org/onap/cps/api/impl/CpServiceImplSpec.groovy

index d1bf49d..d5f0c66 100644 (file)
@@ -29,6 +29,5 @@ java -DDB_HOST=localhost -DDB_USERNAME=cps -DDB_PASSWORD=cps -jar cps-rest/targe
 ```
 
 * Browse
-  * [Swagger UI](http://localhost:8080/swagger-ui/index.html)
-  * OpenAPI Specification in [JSON](http://localhost:8080/api/cps/openapi.json)
-   or [YAML](http://localhost:8080/api/cps/openapi.yaml) format
+  * [Swagger UI](http://localhost:8080/api/cps/swagger-ui/index.html)
+  * [Api Documentation](http://localhost:8080/api/cps/v3/api-docs)
index 6f50cd0..ee37b1e 100644 (file)
@@ -17,6 +17,7 @@
         <hibernate-types.version>2.10.0</hibernate-types.version>
         <spock-core.version>2.0-M2-groovy-3.0</spock-core.version>
         <springboot.version>2.3.3.RELEASE</springboot.version>
+        <springfox.version>3.0.0</springfox.version>
         <swagger.version>2.1.4</swagger.version>
         <yangtools.version>5.0.6</yangtools.version>
     </properties>
@@ -43,9 +44,9 @@
                 <version>${swagger.version}</version>
             </dependency>
             <dependency>
-                <groupId>io.swagger.core.v3</groupId>
-                <artifactId>swagger-jaxrs2</artifactId>
-                <version>${swagger.version}</version>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-boot-starter</artifactId>
+                <version>${springfox.version}</version>
             </dependency>
             <dependency>
                 <groupId>com.vladmihalcea</groupId>
index 4984b61..0fe2e10 100644 (file)
 
     <properties>
         <java.version>11</java.version>
-        <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
-        <maven-dependency-plugin.version>3.1.2</maven-dependency-plugin.version>
-        <maven-replacer-plugin.version>1.5.3</maven-replacer-plugin.version>
         <oparent.version>3.1.0</oparent.version>
         <spring-boot-maven-plugin.version>2.3.3.RELEASE</spring-boot-maven-plugin.version>
-        <swagger-ui.version>3.35.0</swagger-ui.version>
+        <swagger-codegen-maven-plugin.version>3.0.18</swagger-codegen-maven-plugin.version>
     </properties>
 
     <dependencyManagement>
                         </execution>
                     </executions>
                 </plugin>
+                <!-- Swagger code generation. -->
                 <plugin>
-                    <!-- Download Swagger UI webjar. -->
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-dependency-plugin</artifactId>
-                    <version>${maven-dependency-plugin.version}</version>
+                    <groupId>io.swagger.codegen.v3</groupId>
+                    <artifactId>swagger-codegen-maven-plugin</artifactId>
+                    <version>${swagger-codegen-maven-plugin.version}</version>
                     <executions>
                         <execution>
-                            <phase>prepare-package</phase>
                             <goals>
-                                <goal>unpack</goal>
+                                <goal>generate</goal>
                             </goals>
                             <configuration>
-                                <artifactItems>
-                                    <artifactItem>
-                                        <groupId>org.webjars</groupId>
-                                        <artifactId>swagger-ui</artifactId>
-                                        <version>${swagger-ui.version}</version>
-                                    </artifactItem>
-                                </artifactItems>
-                                <outputDirectory>
-                                    ${project.build.directory}/swagger-ui-${swagger-ui.version}
-                                </outputDirectory>
+                                <inputSpec>${project.basedir}/docs/api/swagger/openapi.yml</inputSpec>
+                                <invokerPackage>org.onap.cps.rest.controller</invokerPackage>
+                                <modelPackage>org.onap.cps.rest.model</modelPackage>
+                                <apiPackage>org.onap.cps.rest.api</apiPackage>
+                                <language>spring</language>
+                                <generateSupportingFiles>false</generateSupportingFiles>
+                                <configOptions>
+                                    <sourceFolder>src/gen/java</sourceFolder>
+                                    <dateLibrary>java11</dateLibrary>
+                                    <interfaceOnly>true</interfaceOnly>
+                                    <useTags>true</useTags>
+                                </configOptions>
                             </configuration>
                         </execution>
                     </executions>
                 </plugin>
-                <plugin>
-                    <!-- Copy Swagger UI resources to static resources directory. -->
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-resources-plugin</artifactId>
-                    <version>${maven-resources-plugin.version}</version>
-                    <executions>
-                        <execution>
-                            <id>copy-resources</id>
-                            <phase>prepare-package</phase>
-                            <goals>
-                                <goal>copy-resources</goal>
-                            </goals>
-                            <configuration>
-                                <outputDirectory>${project.build.outputDirectory}/static/swagger-ui
-                                </outputDirectory>
-                                <resources>
-                                    <resource>
-                                        <directory>
-                                            ${project.build.directory}/swagger-ui-${swagger-ui.version}/META-INF/resources/webjars/swagger-ui/${swagger-ui.version}/
-                                        </directory>
-                                        <excludes>
-                                            <exclude>**/*.gz</exclude>
-                                        </excludes>
-                                    </resource>
-                                </resources>
-                            </configuration>
-                        </execution>
-                    </executions>
-                </plugin>
-                <plugin>
-                    <!-- Replace the OpenAPI specification example URL with the local one. -->
-                    <groupId>com.google.code.maven-replacer-plugin</groupId>
-                    <artifactId>replacer</artifactId>
-                    <version>${maven-replacer-plugin.version}</version>
-                    <executions>
-                        <execution>
-                            <phase>prepare-package</phase>
-                            <goals>
-                                <goal>replace</goal>
-                            </goals>
-                        </execution>
-                    </executions>
-                    <configuration>
-                        <file>${project.build.outputDirectory}/static/swagger-ui/index.html</file>
-                        <replacements>
-                            <replacement>
-                                <token>https://petstore.swagger.io/v2/swagger.json</token>
-                                <value>/api/cps/openapi.json</value>
-                            </replacement>
-                        </replacements>
-                    </configuration>
-                </plugin>
             </plugins>
         </pluginManagement>
         <plugins>
index f116c26..9b2ac1e 100644 (file)
@@ -6,13 +6,13 @@ info:
 servers:
   - url: //localhost:8088/
 tags:
-  - name: cps-resource
+  - name: cps-rest
     description: cps Resource
 paths:
   /v1/dataspaces/{dataspace-name}/:
     delete:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Delete the given dataspace
       operationId: deleteDataspace
       parameters:
@@ -41,7 +41,7 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors:
     get:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Read all anchors, given a dataspace
       operationId: getAnchors
       parameters:
@@ -69,7 +69,7 @@ paths:
           content: {}
     post:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Create a new anchor in the given dataspace
       operationId: createAnchor
       parameters:
@@ -86,9 +86,9 @@ paths:
               required:
                 - file
               properties:
-                file:
+                multipartFile:
                   type: string
-                  description: file
+                  description: multipartFile
                   format: binary
         required: true
       responses:
@@ -113,7 +113,7 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}:
     get:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Read an anchor given a anchor and a dataspace
       operationId: getAnchor
       parameters:
@@ -147,7 +147,7 @@ paths:
           content: {}
     delete:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Delete an anchor given a anchor and a dataspace
       operationId: deleteAnchor
       parameters:
@@ -182,7 +182,7 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     get:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Get a node given an anchor for the given dataspace
       operationId: getNodeByDataspaceAndAnchor
       parameters:
@@ -225,7 +225,7 @@ paths:
   /v1/dataspaces/{dataspace-name}/modules:
     get:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Read all yang modules in the store
       operationId: getModule
       parameters:
@@ -263,7 +263,7 @@ paths:
           content: {}
     post:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Create modules for the given dataspace
       operationId: createModules
       parameters:
@@ -280,9 +280,9 @@ paths:
               required:
                 - file
               properties:
-                file:
+                multipartFile:
                   type: string
-                  description: file
+                  description: multipartFile
                   format: binary
         required: true
       responses:
@@ -307,7 +307,7 @@ paths:
   /v1/dataspaces/{dataspace-name}/nodes:
     get:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Get all nodes for a given dataspace using an xpath or schema node identifier
       operationId: getNode
       parameters:
@@ -343,7 +343,7 @@ paths:
       x-codegen-request-body-name: requestBody
     post:
       tags:
-        - cps-resource
+        - cps-rest
       summary: Create a node for a given anchor for the given dataspace
       operationId: createNode
       parameters:
@@ -360,9 +360,9 @@ paths:
               required:
                 - file
               properties:
-                file:
+                multipartFile:
                   type: string
-                  description: file
+                  description: multipartFile
                   format: binary
         required: true
       responses:
index 2d12580..4f8746a 100644 (file)
             <artifactId>swagger-annotations</artifactId>\r
         </dependency>\r
         <dependency>\r
-            <groupId>io.swagger.core.v3</groupId>\r
-            <artifactId>swagger-jaxrs2</artifactId>\r
+            <groupId>io.springfox</groupId>\r
+            <artifactId>springfox-boot-starter</artifactId>\r
         </dependency>\r
+\r
         <dependency>\r
             <groupId>org.springframework.boot</groupId>\r
             <artifactId>spring-boot-starter-test</artifactId>\r
                 </exclusion>\r
             </exclusions>\r
         </dependency>\r
+        <!-- T E S T   D E P E N D E N C I E S -->\r
+        <dependency>\r
+            <groupId>org.codehaus.groovy</groupId>\r
+            <artifactId>groovy</artifactId>\r
+            <scope>test</scope>\r
+        </dependency>\r
+        <dependency>\r
+            <groupId>org.spockframework</groupId>\r
+            <artifactId>spock-core</artifactId>\r
+            <scope>test</scope>\r
+        </dependency>\r
+        <dependency>\r
+            <groupId>cglib</groupId>\r
+            <artifactId>cglib-nodep</artifactId>\r
+            <scope>test</scope>\r
+        </dependency>\r
     </dependencies>\r
 \r
     <build>\r
                 <groupId>org.springframework.boot</groupId>\r
                 <artifactId>spring-boot-maven-plugin</artifactId>\r
             </plugin>\r
+            <!-- Swagger code generation. -->\r
             <plugin>\r
-                <!-- Download Swagger UI webjar. -->\r
-                <groupId>org.apache.maven.plugins</groupId>\r
-                <artifactId>maven-dependency-plugin</artifactId>\r
-            </plugin>\r
-            <plugin>\r
-                <!-- Copy Swagger UI resources to static resources directory. -->\r
-                <groupId>org.apache.maven.plugins</groupId>\r
-                <artifactId>maven-resources-plugin</artifactId>\r
-            </plugin>\r
-            <plugin>\r
-                <!-- Replace the OpenAPI specification example URL with the local one. -->\r
-                <groupId>com.google.code.maven-replacer-plugin</groupId>\r
-                <artifactId>replacer</artifactId>\r
+                <groupId>io.swagger.codegen.v3</groupId>\r
+                <artifactId>swagger-codegen-maven-plugin</artifactId>\r
             </plugin>\r
         </plugins>\r
     </build>\r
index a577af7..90c287c 100644 (file)
@@ -26,13 +26,13 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import javax.persistence.PersistenceException;
-import org.hibernate.exception.ConstraintViolationException;
+import javax.validation.Valid;
 import org.onap.cps.api.CpService;
+import org.onap.cps.exceptions.CpsException;
+import org.onap.cps.exceptions.CpsValidationException;
+import org.onap.cps.rest.api.CpsRestApi;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.DeleteMapping;
@@ -44,38 +44,69 @@ import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
 @RestController
-public class CpsRestController {
+public class CpsRestController implements CpsRestApi {
 
     @Autowired
     private CpService cpService;
 
+    @Override
+    public ResponseEntity<Object> createAnchor(@Valid MultipartFile multipartFile, String dataspaceName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> createModules(@Valid MultipartFile multipartFile, String dataspaceName) {
+        final File fileToParse = saveToFile(multipartFile);
+        final SchemaContext schemaContext = cpService.parseAndValidateModel(fileToParse);
+        cpService.storeSchemaContext(schemaContext, dataspaceName);
+        return new ResponseEntity<>("Resource successfully created", HttpStatus.CREATED);
+    }
+
+    @Override
+    public ResponseEntity<Object> createNode(@Valid MultipartFile multipartFile, String dataspaceName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> deleteAnchor(String dataspaceName, String anchorName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> deleteDataspace(String dataspaceName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> getAnchor(String dataspaceName, String anchorName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> getAnchors(String dataspaceName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> getModule(String dataspaceName, @Valid String namespaceName, @Valid String revision) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> getNode(@Valid String body, String dataspaceName) {
+        return null;
+    }
+
+    @Override
+    public ResponseEntity<Object> getNodeByDataspaceAndAnchor(@Valid String body, String dataspaceName,
+        String anchorName) {
+        return null;
+    }
+
     /*
     Old rest endpoints before contract first approach (Need to be removed).
      */
 
-    /**
-     * Upload a yang model file.
-     *
-     * @param uploadedFile the yang model Multipart File.
-     * @param dataspaceName the dataspace name linked to the model.
-     * @return a ResponseEntity.
-     */
-    @PostMapping("/dataspaces/{dataspace_name}/modules")
-    public final ResponseEntity<String> uploadYangModelFile(
-        @RequestParam("file") MultipartFile uploadedFile,
-        @PathVariable("dataspace_name") String dataspaceName) {
-        try {
-            final File fileToParse = saveToFile(uploadedFile);
-            final SchemaContext schemaContext = cpService.parseAndValidateModel(fileToParse);
-            cpService.storeSchemaContext(schemaContext, dataspaceName);
-            return new ResponseEntity<String>("Resource successfully created", HttpStatus.CREATED);
-        } catch (final YangParserException | ConstraintViolationException e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
-        } catch (final Exception e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
-        }
-    }
-
     /**
      * Upload a JSON file.
      *
@@ -85,16 +116,11 @@ public class CpsRestController {
     @PostMapping("/upload-yang-json-data-file")
     public final ResponseEntity<String> uploadYangJsonDataFile(
         @RequestParam("file") MultipartFile uploadedFile) {
-        try {
-            validateJsonStructure(uploadedFile);
-            final int persistenceObjectId = cpService.storeJsonStructure(new String(uploadedFile.getBytes()));
-            return new ResponseEntity<String>(
-                "Object stored in CPS with identity: " + persistenceObjectId, HttpStatus.OK);
-        } catch (final JsonSyntaxException e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
-        } catch (final Exception e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
-        }
+        validateJsonStructure(uploadedFile);
+        final int persistenceObjectId = cpService.storeJsonStructure(getJsonString(uploadedFile));
+        return new ResponseEntity<String>(
+            "Object stored in CPS with identity: " + persistenceObjectId, HttpStatus.OK);
+
     }
 
     /**
@@ -106,13 +132,7 @@ public class CpsRestController {
     @GetMapping("/json-object/{id}")
     public final ResponseEntity<String> getJsonObjectById(
         @PathVariable("id") final int jsonObjectId) {
-        try {
-            return new ResponseEntity<String>(cpService.getJsonById(jsonObjectId), HttpStatus.OK);
-        } catch (final PersistenceException e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.NOT_FOUND);
-        } catch (final Exception e) {
-            return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
-        }
+        return new ResponseEntity<String>(cpService.getJsonById(jsonObjectId), HttpStatus.OK);
     }
 
     /**
@@ -124,30 +144,39 @@ public class CpsRestController {
     @DeleteMapping("json-object/{id}")
     public final ResponseEntity<Object> deleteJsonObjectById(
         @PathVariable("id") final int jsonObjectId) {
+        cpService.deleteJsonById(jsonObjectId);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    private static void validateJsonStructure(final MultipartFile multipartFile) {
         try {
-            cpService.deleteJsonById(jsonObjectId);
-            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
-        } catch (final EmptyResultDataAccessException e) {
-            return new ResponseEntity<>(HttpStatus.NOT_FOUND.toString(), HttpStatus.NOT_FOUND);
-        } catch (final Exception e) {
-            return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+            final Gson gson = new Gson();
+            gson.fromJson(getJsonString(multipartFile), Object.class);
+        } catch (JsonSyntaxException e) {
+            throw new CpsValidationException("Not a valid JSON file.", e);
         }
     }
 
-    private static final void validateJsonStructure(final MultipartFile jsonFile)
-        throws JsonSyntaxException, IOException {
-        final Gson gson = new Gson();
-        gson.fromJson(new String(jsonFile.getBytes()), Object.class);
-    }
+    private static File saveToFile(final MultipartFile multipartFile) {
+        try {
+            final File file = File.createTempFile("tempFile", ".yang");
+            file.deleteOnExit();
 
-    private static final File saveToFile(final MultipartFile multipartFile)
-        throws IOException {
-        final File file = File.createTempFile("tempFile", ".yang");
-        file.deleteOnExit();
+            try (OutputStream outputStream = new FileOutputStream(file)) {
+                outputStream.write(multipartFile.getBytes());
+            }
+            return file;
 
-        try (OutputStream outputStream = new FileOutputStream(file)) {
-            outputStream.write(multipartFile.getBytes());
+        } catch (IOException e) {
+            throw new CpsException(e);
+        }
+    }
+
+    private static String getJsonString(final MultipartFile multipartFile) {
+        try {
+            return new String(multipartFile.getBytes());
+        } catch (IOException e) {
+            throw new CpsException(e);
         }
-        return file;
     }
 }
\ No newline at end of file
diff --git a/cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java b/cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java
new file mode 100644 (file)
index 0000000..9cf4935
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Pantheon.tech
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.rest.exceptions;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.hibernate.exception.ConstraintViolationException;
+import org.onap.cps.exceptions.CpsException;
+import org.onap.cps.exceptions.CpsNotFoundException;
+import org.onap.cps.exceptions.CpsValidationException;
+import org.onap.cps.rest.controller.CpsRestController;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice(assignableTypes = {CpsRestController.class})
+public class CpsRestExceptionHandler {
+
+    /**
+     * Default exception handler.
+     *
+     * @param exception the exception to handle
+     * @return response with response code 500.
+     */
+    @ExceptionHandler
+    public ResponseEntity<Object> handleInternalErrorException(Exception exception) {
+        return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
+    }
+
+    /*
+        TODO: Rid off extra dependencies.
+
+        Generic exception handler and CpsException (incl. children) are the only
+        exceptions to be handled here. All the other exceptions which require a special
+        treatment should be rethrown as CpsException in the place of occurrence ->
+        e.g. persistence exceptions are to be handled in cps-ri module.
+     */
+
+    @ExceptionHandler({ConstraintViolationException.class})
+    public ResponseEntity<Object> handleBadRequestException(Exception exception) {
+        return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
+    }
+
+    @ExceptionHandler({EmptyResultDataAccessException.class})
+    public ResponseEntity<Object> handleNotFoundException(Exception exception) {
+        return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
+    }
+
+    @ExceptionHandler({CpsException.class})
+    public ResponseEntity<Object> handleCpsException(CpsException exception) {
+        return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage(), extractDetails(exception));
+    }
+
+    @ExceptionHandler({CpsValidationException.class})
+    public ResponseEntity<Object> handleCpsValidationException(CpsException exception) {
+        return buildErrorResponse(HttpStatus.BAD_REQUEST, exception.getMessage(), extractDetails(exception));
+    }
+
+    @ExceptionHandler({CpsNotFoundException.class})
+    public ResponseEntity<Object> handleCpsNotFoundException(CpsException exception) {
+        return buildErrorResponse(HttpStatus.NOT_FOUND, exception.getMessage(), extractDetails(exception));
+    }
+
+    private static ResponseEntity<Object> buildErrorResponse(HttpStatus status, Exception exception) {
+        return buildErrorResponse(status, exception.getMessage(), ExceptionUtils.getStackTrace(exception));
+    }
+
+    private static ResponseEntity<Object> buildErrorResponse(HttpStatus status, String message, String details) {
+        return new ResponseEntity<>(
+            ErrorMessage.builder().status(status.toString()).message(message).details(details).build(),
+            status
+        );
+    }
+
+    private static String extractDetails(CpsException exception) {
+        return exception.getCause() == null
+            ? exception.getDetails()
+            : ExceptionUtils.getStackTrace(exception.getCause());
+    }
+}
diff --git a/cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/ErrorMessage.java b/cps/cps-rest/src/main/java/org/onap/cps/rest/exceptions/ErrorMessage.java
new file mode 100644 (file)
index 0000000..1f2a0b7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Pantheon.tech
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.rest.exceptions;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Temporary POJO class used as error response model.
+ * TODO: To replace with model class generated from Open API specification.
+ *
+ */
+@Builder
+@Data
+public class ErrorMessage {
+    private String status;
+    private String message;
+    private String details;
+}
+
diff --git a/cps/cps-rest/src/main/java/org/onap/cps/swagger/config/SpringFoxConfig.java b/cps/cps-rest/src/main/java/org/onap/cps/swagger/config/SpringFoxConfig.java
new file mode 100644 (file)
index 0000000..73e1795
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Bell Canada. All rights reserved.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.swagger.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+/**
+ * Swagger configuration.
+ */
+@Configuration
+public class SpringFoxConfig {
+
+    /**
+     * Define api configuration.
+     */
+    @Bean
+    public Docket api() {
+        return new Docket(DocumentationType.OAS_30)
+                       .select()
+                       .apis(RequestHandlerSelectors.any())
+                       .paths(PathSelectors.any())
+                       .build();
+    }
+}
index 84f3f79..e8af0bc 100644 (file)
@@ -1,7 +1,7 @@
 server:\r
-  port: 8080\r
-  servlet:\r
-     context-path: /api/cps\r
+    port: 8080\r
+    servlet:\r
+        context-path: /api/cps\r
 \r
 spring:\r
     main:\r
@@ -21,8 +21,6 @@ spring:
         password: ${DB_PASSWORD}\r
         driverClassName: org.postgresql.Driver\r
         initialization-mode: always\r
-    jersey:\r
-        type: filter\r
 \r
 # Actuator\r
 management:\r
index 2f735ab..4d94a46 100644 (file)
@@ -36,7 +36,7 @@ public interface CpService {
      * @param yangModelContent the input stream
      * @return the schema context
      */
-    SchemaContext parseAndValidateModel(final String yangModelContent) throws IOException, YangParserException;
+    SchemaContext parseAndValidateModel(final String yangModelContent);
 
     /**
      * Parse and validate a file representing a yang model to generate a schema context.
@@ -44,7 +44,7 @@ public interface CpService {
      * @param yangModelFile the yang file
      * @return the schema context
      */
-    SchemaContext parseAndValidateModel(final File yangModelFile) throws IOException, YangParserException;
+    SchemaContext parseAndValidateModel(final File yangModelFile);
 
     /**
      * Store schema context for a yang model.
index 45aad3e..c33746e 100644 (file)
@@ -26,6 +26,8 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.util.Optional;
 import org.onap.cps.api.CpService;
+import org.onap.cps.exceptions.CpsException;
+import org.onap.cps.exceptions.CpsValidationException;
 import org.onap.cps.spi.DataPersistencyService;
 import org.onap.cps.spi.ModelPersistencyService;
 import org.onap.cps.utils.YangUtils;
@@ -47,18 +49,28 @@ public class CpServiceImpl implements CpService {
     private DataPersistencyService dataPersistencyService;
 
     @Override
-    public final SchemaContext parseAndValidateModel(final String yangModelContent) throws IOException,
-        YangParserException {
-        final File tempFile = File.createTempFile("yang", ".yang");
-        try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
-            writer.write(yangModelContent);
+    public final SchemaContext parseAndValidateModel(final String yangModelContent) {
+
+        try {
+            final File tempFile = File.createTempFile("yang", ".yang");
+            try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
+                writer.write(yangModelContent);
+            }
+            return parseAndValidateModel(tempFile);
+        } catch (IOException e) {
+            throw new CpsException(e);
         }
-        return parseAndValidateModel(tempFile);
     }
 
     @Override
-    public final SchemaContext parseAndValidateModel(final File yangModelFile) throws IOException, YangParserException {
-        return YangUtils.parseYangModelFile(yangModelFile);
+    public final SchemaContext parseAndValidateModel(final File yangModelFile) {
+        try {
+            return YangUtils.parseYangModelFile(yangModelFile);
+        } catch (YangParserException e) {
+            throw new CpsValidationException("Yang file validation failed", e.getMessage());
+        } catch (IOException e) {
+            throw new CpsException(e);
+        }
     }
 
     @Override
diff --git a/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java b/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java
new file mode 100644 (file)
index 0000000..b54453c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Pantheon.tech
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+import lombok.Getter;
+import org.springframework.core.NestedExceptionUtils;
+
+/**
+ * CP Service exception.
+ */
+public class CpsException extends RuntimeException {
+
+    @Getter
+    String details;
+
+    /**
+     * Constructor.
+     *
+     * @param cause the cause of the exception
+     */
+    public CpsException(Throwable cause) {
+        super(cause.getMessage(), cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param cause   the cause of the exception
+     */
+    public CpsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public CpsException(String message, String details) {
+        super(message);
+        this.details = details;
+    }
+}
diff --git a/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java b/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java
new file mode 100644 (file)
index 0000000..f44fe80
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Pantheon.tech
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+import lombok.Getter;
+
+/**
+ * CP Service exception. Indicates the requested data being absent.
+ */
+public class CpsNotFoundException extends CpsException {
+
+    /**
+     * Constructor.
+     *
+     * @param cause the cause of the exception
+     */
+    public CpsNotFoundException(Throwable cause) {
+        super(cause.getMessage(), cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param cause   the cause of the exception
+     */
+    public CpsNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public CpsNotFoundException(String message, String details) {
+        super(message, details);
+    }
+}
diff --git a/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java b/cps/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java
new file mode 100644 (file)
index 0000000..dba9c16
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Pantheon.tech
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+/**
+ * CP Service exception. Indicates the parameter validation failure.
+ */
+public class CpsValidationException extends CpsException {
+
+    /**
+     * Constructor.
+     *
+     * @param cause the cause of the exception
+     */
+    public CpsValidationException(Throwable cause) {
+        super(cause.getMessage(), cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param cause   the cause of the exception
+     */
+    public CpsValidationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public CpsValidationException(String message, String details) {
+        super(message, details);
+    }
+}
index 5e6fccb..5f42810 100644 (file)
 package org.onap.cps.api.impl
 
 import org.onap.cps.TestUtils
+import org.onap.cps.exceptions.CpsValidationException
 import org.onap.cps.spi.DataPersistencyService
-
 import org.opendaylight.yangtools.yang.common.Revision
 import org.opendaylight.yangtools.yang.model.api.SchemaContext
-import org.opendaylight.yangtools.yang.model.parser.api.YangParserException
 import spock.lang.Specification
 
 class CpServiceImplSpec extends Specification {
@@ -73,8 +72,8 @@ class CpServiceImplSpec extends Specification {
             File file = new File(ClassLoader.getSystemClassLoader().getResource('invalid.yang').getFile())
         when: 'the model is parsed and validated'
             objectUnderTest.parseAndValidateModel(file)
-        then: 'a YangParserException is thrown'
-            thrown(YangParserException)
+        then: 'a CpsValidationException is thrown'
+            thrown(CpsValidationException)
     }
 
     def 'Store a SchemaContext'() {