Enrichment cordinator service, added job validation 19/4719/4
authorPatrikBuhr <patrik.buhr@est.tech>
Fri, 18 Sep 2020 13:13:03 +0000 (15:13 +0200)
committerPatrikBuhr <patrik.buhr@est.tech>
Mon, 21 Sep 2020 08:01:20 +0000 (10:01 +0200)
Added validation of EI job data agains the registerred json schema.
Improved tests.
Improved documentation.

Change-Id: I86fe031b22aa0378d82181a019e8f0743f283e8a
Issue-ID: NONRTRIC-173
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
13 files changed:
enrichment-coordinator-service/docs/api.yaml
enrichment-coordinator-service/pom.xml
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/MultiMap.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java

index bb34e2b..2fbf934 100644 (file)
@@ -17,8 +17,7 @@ paths:
     get:
       tags:
         - A1-E Enrichment Data Consumer API
-      summary: Query EI type identifiers
-      description: DETAILS TBD
+      summary: EI type identifiers
       operationId: getEiTypeIdentifiersUsingGET
       produces:
         - application/json
@@ -40,8 +39,7 @@ paths:
     get:
       tags:
         - A1-E Enrichment Data Consumer API
-      summary: Definitions for an individual EI Type
-      description: Query EI type
+      summary: Individual EI type
       operationId: getEiTypeUsingGET
       produces:
         - application/json
@@ -69,8 +67,7 @@ paths:
     get:
       tags:
         - A1-E Enrichment Data Consumer API
-      summary: Query EI job identifiers
-      description: Returns the identifiers for an EI Type
+      summary: EI job identifiers
       operationId: getEiJobIdsUsingGET
       produces:
         - application/json
@@ -88,7 +85,7 @@ paths:
             type: string
       responses:
         '200':
-          description: EI type
+          description: EI job identifiers
           schema:
             type: array
             items:
@@ -139,7 +136,6 @@ paths:
       tags:
         - A1-E Enrichment Data Consumer API
       summary: Individual EI Job
-      description: Create or update an EI Job
       operationId: putIndividualEiJobUsingPUT
       consumes:
         - application/json
@@ -184,7 +180,6 @@ paths:
       tags:
         - A1-E Enrichment Data Consumer API
       summary: Individual EI Job
-      description: Delete an EI job
       operationId: deleteIndividualEiJobUsingDELETE
       produces:
         - application/json
@@ -254,8 +249,7 @@ paths:
     get:
       tags:
         - Enrichment Data Producer API
-      summary: Query EI producer identifiers
-      description: DETAILS TBD
+      summary: EI producer identifiers
       operationId: getEiProducerIdentifiersUsingGET
       produces:
         - application/json
@@ -277,8 +271,7 @@ paths:
     get:
       tags:
         - Enrichment Data Producer API
-      summary: Job definition for an individual EI producer
-      description: Query EI jobs
+      summary: Individual EI producer
       operationId: getEiProducerUsingGET
       produces:
         - application/json
@@ -292,7 +285,7 @@ paths:
         '200':
           description: EI Jobs
           schema:
-            $ref: '#/definitions/producer_ei_type_info'
+            $ref: '#/definitions/producer_registration_info'
         '401':
           description: Unauthorized
         '403':
@@ -305,8 +298,7 @@ paths:
     put:
       tags:
         - Enrichment Data Producer API
-      summary: Definitions for an individual EI producer
-      description: Put EI producer
+      summary: Individual EI producer
       operationId: putEiProducerUsingPUT
       consumes:
         - application/json
@@ -343,8 +335,7 @@ paths:
     delete:
       tags:
         - Enrichment Data Producer API
-      summary: Individual EI Producer
-      description: Delete an EI Producer
+      summary: Individual EI producer
       operationId: deleteEiProducerUsingDELETE
       produces:
         - application/json
@@ -376,8 +367,8 @@ paths:
     get:
       tags:
         - Enrichment Data Producer API
-      summary: Job definition for an individual EI producer
-      description: Query EI producer jobs
+      summary: EI job definitions
+      description: EI job definitions for one EI producer
       operationId: getEiProducerJobsUsingGET
       produces:
         - application/json
@@ -407,8 +398,7 @@ paths:
     get:
       tags:
         - Enrichment Data Producer API
-      summary: Query EI type identifiers
-      description: DETAILS TBD
+      summary: EI type identifiers
       operationId: getEiTypeIdentifiersUsingGET_1
       produces:
         - application/json
@@ -430,8 +420,7 @@ paths:
     get:
       tags:
         - Enrichment Data Producer API
-      summary: Definitions for an individual EI Type
-      description: Query EI type
+      summary: Individual EI Type
       operationId: getEiTypeUsingGET_1
       produces:
         - application/json
@@ -459,7 +448,7 @@ paths:
     post:
       tags:
         - Producer Simulator
-      summary: Callback for job creation
+      summary: Callback for EI job creation
       operationId: jobCreatedCallbackUsingPOST
       consumes:
         - application/json
@@ -490,7 +479,7 @@ paths:
     post:
       tags:
         - Producer Simulator
-      summary: Callback for job deletion
+      summary: Callback for EI job deletion
       operationId: jobDeletedCallbackUsingPOST
       consumes:
         - application/json
@@ -522,12 +511,12 @@ definitions:
     type: object
     required:
       - job_data
-      - owner
+      - job_owner
     properties:
       job_data:
         type: object
         description: EI Type specific job data
-      owner:
+      job_owner:
         type: string
         description: Identity of the owner of the job
     title: ei_job_info
@@ -540,8 +529,8 @@ definitions:
       operational_state:
         type: string
         description: |-
-          Operational state, values: 
-          ENABLED: TBD 
+          Operational state, values:
+          ENABLED: TBD
           DISABLED: TBD.
         enum:
           - ENABLED
@@ -562,37 +551,37 @@ definitions:
       detail:
         type: string
         example: EI job type not found
-        description: ' A human-readable explanation specific to this occurrence of the problem.'
+        description: A human-readable explanation specific to this occurrence of the problem.
       status:
         type: integer
         format: int32
-        example: 503
-        description: 'The HTTP status code generated by the origin server for this occurrence of the problem. '
+        example: 404
+        description: The HTTP status code generated by the origin server for this occurrence of the problem.
     title: error_information
     description: 'Problem as defined in https://tools.ietf.org/html/rfc7807'
   producer_ei_job_request:
     type: object
     required:
-      - identity
+      - ei_job_identity
     properties:
-      identity:
-        type: string
-        description: Json for the job data
-      job_data:
+      ei_job_data:
         type: object
         description: Json for the job data
-      type_identity:
+      ei_job_identity:
+        type: string
+        description: Idenitity of the EI job
+      ei_type_identity:
         type: string
         description: Type idenitity for the job
     title: producer_ei_job_request
-    description: Information EI job start
+    description: The body of the EI producer callbacks for EI job creation and deletion
   producer_ei_type_info:
     type: object
     properties:
-      job_data_schema:
+      ei_job_data_schema:
         type: object
         description: Json schema for the job data
-      producer_ids:
+      ei_producer_ids:
         type: array
         description: Registered producers
         items:
@@ -604,25 +593,25 @@ definitions:
     required:
       - ei_type_identity
     properties:
+      ei_job_data_schema:
+        type: object
+        description: Json schema for the job data
       ei_type_identity:
         type: string
         description: EI type identity
-      job_data_schema:
-        type: object
-        description: Json schema for the job data
     title: producer_ei_type_registration_info
     description: Information for an EI type
   producer_registration_info:
     type: object
     required:
-      - job_creation_callback_url
-      - job_deletion_callback_url
+      - ei_job_creation_callback_url
+      - ei_job_deletion_callback_url
       - supported_ei_types
     properties:
-      job_creation_callback_url:
+      ei_job_creation_callback_url:
         type: string
         description: callback for job creation
-      job_deletion_callback_url:
+      ei_job_deletion_callback_url:
         type: string
         description: callback for job deletion
       supported_ei_types:
@@ -631,5 +620,5 @@ definitions:
         items:
           $ref: '#/definitions/producer_ei_type_registration_info'
     title: producer_registration_info
-    description: Information for an EI Producer
+    description: Information for an EI producer
 
index 3f156a7..a069c0d 100644 (file)
             <groupId>org.glassfish.jersey.inject</groupId>
             <artifactId>jersey-hk2</artifactId>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/com.github.erosb/everit-json-schema -->
+        <dependency>
+            <groupId>com.github.erosb</groupId>
+            <artifactId>everit-json-schema</artifactId>
+            <version>1.12.1</version>
+        </dependency>
         <!-- Actuator dependencies -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
index ee80183..6fa38aa 100644 (file)
@@ -23,10 +23,6 @@ package org.oransc.enrichment;
 import com.fasterxml.classmate.TypeResolver;
 import com.google.common.base.Predicates;
 
-import org.oransc.enrichment.clients.ProducerJobInfo;
-import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
-import org.oransc.enrichment.controllers.producer.ProducerEiTypeInfo;
-import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@@ -67,17 +63,13 @@ public class SwaggerConfig extends WebMvcConfigurationSupport {
     public Docket api(TypeResolver resolver) {
         return new Docket(DocumentationType.SWAGGER_2) //
             .apiInfo(apiInfo()) //
-            .additionalModels(resolver.resolve(ConsumerEiJobInfo.class)) //
-            .additionalModels(resolver.resolve(ProducerRegistrationInfo.class)) //
-            .additionalModels(resolver.resolve(ProducerEiTypeInfo.class)) //
-            .additionalModels(resolver.resolve(ProducerJobInfo.class)) //
             .select() //
             .apis(RequestHandlerSelectors.any()) //
             .paths(PathSelectors.any()) //
-            .paths(Predicates.not(PathSelectors.regex("/error"))) //
-            // this endpoint is not implemented, but was visible for Swagger
-            .paths(Predicates.not(PathSelectors.regex("/actuator.*"))) //
-            // this endpoint is implemented by spring framework, exclude for now
+            .paths(Predicates.not(PathSelectors.regex("/error"))) // this endpoint is not implemented, but was
+                                                                  // visible for Swagger
+            .paths(Predicates.not(PathSelectors.regex("/actuator.*"))) // this endpoint is implemented by spring
+                                                                       // framework, exclude for now
             .build();
     }
 
index 60762b9..ff91221 100644 (file)
@@ -31,22 +31,24 @@ import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiType;
 
 @Gson.TypeAdapters
-@ApiModel(value = "producer_ei_job_request", description = "Information EI job start")
+@ApiModel(
+    value = "producer_ei_job_request",
+    description = "The body of the EI producer callbacks for EI job creation and deletion")
 public class ProducerJobInfo {
 
-    @ApiModelProperty(value = "Json for the job data", required = true)
-    @SerializedName("identity")
-    @JsonProperty("identity")
+    @ApiModelProperty(value = "Idenitity of the EI job", required = true)
+    @SerializedName("ei_job_identity")
+    @JsonProperty("ei_job_identity")
     public String id;
 
     @ApiModelProperty(value = "Type idenitity for the job")
-    @SerializedName("type_identity")
-    @JsonProperty("type_identity")
+    @SerializedName("ei_type_identity")
+    @JsonProperty("ei_type_identity")
     public String typeId;
 
     @ApiModelProperty(value = "Json for the job data")
-    @SerializedName("job_data")
-    @JsonProperty("job_data")
+    @SerializedName("ei_job_data")
+    @JsonProperty("ei_job_data")
     public Object jobData;
 
     public ProducerJobInfo(Object jobData, String id, String typeId) {
index edaa3bb..c90a649 100644 (file)
@@ -61,7 +61,7 @@ public class ErrorResponse {
         }
 
         @ApiModelProperty(
-            example = "503",
+            example = "404",
             value = "The HTTP status code generated by the origin server for this occurrence of the problem.")
         public Integer getStatus() {
             return status;
@@ -73,7 +73,6 @@ public class ErrorResponse {
         public String getDetail() {
             return this.detail;
         }
-
     }
 
     @ApiModelProperty(value = "message")
index c43a495..a74e487 100644 (file)
@@ -20,9 +20,9 @@
 
 package org.oransc.enrichment.controllers.consumer;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonObject;
 
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -34,9 +34,13 @@ import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.everit.json.schema.Schema;
+import org.everit.json.schema.loader.SchemaLoader;
+import org.json.JSONObject;
 import org.oransc.enrichment.clients.ProducerCallbacks;
 import org.oransc.enrichment.configuration.ApplicationConfig;
 import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.exceptions.ServiceException;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiJobs;
 import org.oransc.enrichment.repository.EiType;
@@ -79,7 +83,7 @@ public class ConsumerController {
         .create(); //
 
     @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD")
+    @ApiOperation(value = "EI type identifiers", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(
@@ -99,7 +103,7 @@ public class ConsumerController {
     }
 
     @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Definitions for an individual EI Type", notes = "Query EI type")
+    @ApiOperation(value = "Individual EI type", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "EI type", response = ConsumerEiTypeInfo.class), //
@@ -121,7 +125,7 @@ public class ConsumerController {
     @GetMapping(
         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Query EI job identifiers", notes = "Returns the EI Job identifiers for an EI Type")
+    @ApiOperation(value = "EI job identifiers", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(
@@ -206,7 +210,7 @@ public class ConsumerController {
     @DeleteMapping(
         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Individual EI Job", notes = "Delete EI job")
+    @ApiOperation(value = "Individual EI Job", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "Not used", response = void.class),
@@ -232,7 +236,7 @@ public class ConsumerController {
         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", //
         produces = MediaType.APPLICATION_JSON_VALUE, //
         consumes = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Individual EI Job", notes = "Create or update an EI Job")
+    @ApiOperation(value = "Individual EI Job", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 201, message = "Job created", response = void.class), //
@@ -258,11 +262,24 @@ public class ConsumerController {
         }
     }
 
-    private void validateJobData(Object schemaObj, Object json) {
-        if (schemaObj instanceof JsonObject) {
-            JsonObject schema = (JsonObject) schemaObj;
-            logger.debug("schema {} json {}", schema, json);
+    private void validateJobData(Object schemaObj, Object object) throws ServiceException {
+        if (schemaObj == null) {
+            return; // schema is optional for now
+        }
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+
+            String schemaAsString = mapper.writeValueAsString(schemaObj);
+            JSONObject schemaJSON = new JSONObject(schemaAsString);
+            Schema schema = SchemaLoader.load(schemaJSON);
+
+            String objectAsString = mapper.writeValueAsString(object);
+            JSONObject json = new JSONObject(objectAsString);
+            schema.validate(json);
+        } catch (Exception e) {
+            throw new ServiceException("Json validation failure", e);
         }
+
     }
 
     // Status TBD
index 515c616..8255c43 100644 (file)
@@ -33,8 +33,8 @@ import org.immutables.gson.Gson;
 public class ConsumerEiJobInfo {
 
     @ApiModelProperty(value = "Identity of the owner of the job", required = true)
-    @SerializedName("owner")
-    @JsonProperty(value = "owner", required = true)
+    @SerializedName("job_owner")
+    @JsonProperty(value = "job_owner", required = true)
     public String owner;
 
     @ApiModelProperty(value = "EI Type specific job data", required = true)
index b88e047..c24e559 100644 (file)
@@ -81,7 +81,7 @@ public class ProducerController {
     ProducerCallbacks producerCallbacks;
 
     @GetMapping(path = ProducerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD")
+    @ApiOperation(value = "EI type identifiers", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(
@@ -101,7 +101,7 @@ public class ProducerController {
     }
 
     @GetMapping(path = ProducerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Definitions for an individual EI Type", notes = "Query EI type")
+    @ApiOperation(value = "Individual EI Type", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "EI type", response = ProducerEiTypeInfo.class), //
@@ -121,7 +121,7 @@ public class ProducerController {
     }
 
     @GetMapping(path = ProducerConsts.API_ROOT + "/eiproducers", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Query EI producer identifiers", notes = "DETAILS TBD")
+    @ApiOperation(value = "EI producer identifiers", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(
@@ -143,10 +143,10 @@ public class ProducerController {
     @GetMapping(
         path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Definition for an individual EI producer", notes = "Query EI jobs")
+    @ApiOperation(value = "Individual EI producer", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "EI Jobs", response = ProducerEiTypeInfo.class), //
+            @ApiResponse(code = 200, message = "EI Jobs", response = ProducerRegistrationInfo.class), //
             @ApiResponse(
                 code = 404,
                 message = "Enrichment Information producer is not found",
@@ -165,7 +165,7 @@ public class ProducerController {
     @GetMapping(
         path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}/eijobs",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Job definitions for an individual EI producer", notes = "Query EI producer jobs")
+    @ApiOperation(value = "EI job definitions", notes = "EI job definitions for one EI producer")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "EI jobs", response = ProducerJobInfo.class, responseContainer = "List"), //
@@ -194,7 +194,7 @@ public class ProducerController {
     @PutMapping(
         path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Individual EI producer", notes = "Put EI producer")
+    @ApiOperation(value = "Individual EI producer", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 201, message = "Producer created", response = void.class), //
@@ -220,7 +220,7 @@ public class ProducerController {
     @DeleteMapping(
         path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}",
         produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Individual EI Producer", notes = "Delete an EI Producer")
+    @ApiOperation(value = "Individual EI producer", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "Not used", response = void.class),
index 1c9167b..9a82161 100644 (file)
@@ -35,13 +35,13 @@ import org.immutables.gson.Gson;
 public class ProducerEiTypeInfo {
 
     @ApiModelProperty(value = "Json schema for the job data")
-    @SerializedName("job_data_schema")
-    @JsonProperty("job_data_schema")
+    @SerializedName("ei_job_data_schema")
+    @JsonProperty("ei_job_data_schema")
     public Object jobDataSchema;
 
     @ApiModelProperty(value = "Registered producers")
-    @SerializedName("producer_ids")
-    @JsonProperty(value = "producer_ids", required = true)
+    @SerializedName("ei_producer_ids")
+    @JsonProperty(value = "ei_producer_ids", required = true)
     public Collection<String> producerIds;
 
     public ProducerEiTypeInfo(Object jobDataSchema, Collection<String> producers) {
index c48c716..1ef2ed4 100644 (file)
@@ -31,7 +31,7 @@ import java.util.Collection;
 import org.immutables.gson.Gson;
 
 @Gson.TypeAdapters
-@ApiModel(value = "producer_registration_info", description = "Information for an EI Producer")
+@ApiModel(value = "producer_registration_info", description = "Information for an EI producer")
 public class ProducerRegistrationInfo {
 
     @Gson.TypeAdapters
@@ -44,8 +44,8 @@ public class ProducerRegistrationInfo {
         public String eiTypeId;
 
         @ApiModelProperty(value = "Json schema for the job data")
-        @SerializedName("job_data_schema")
-        @JsonProperty("job_data_schema")
+        @SerializedName("ei_job_data_schema")
+        @JsonProperty("ei_job_data_schema")
         public Object jobDataSchema;
 
         public ProducerEiTypeRegistrationInfo(Object jobDataSchema, String eiTypeId) {
@@ -63,13 +63,13 @@ public class ProducerRegistrationInfo {
     public Collection<ProducerEiTypeRegistrationInfo> types;
 
     @ApiModelProperty(value = "callback for job creation", required = true)
-    @SerializedName("job_creation_callback_url")
-    @JsonProperty(value = "job_creation_callback_url", required = true)
+    @SerializedName("ei_job_creation_callback_url")
+    @JsonProperty(value = "ei_job_creation_callback_url", required = true)
     public String jobCreationCallbackUrl;
 
     @ApiModelProperty(value = "callback for job deletion", required = true)
-    @SerializedName("job_deletion_callback_url")
-    @JsonProperty(value = "job_deletion_callback_url", required = true)
+    @SerializedName("ei_job_deletion_callback_url")
+    @JsonProperty(value = "ei_job_deletion_callback_url", required = true)
     public String jobDeletionCallbackUrl;
 
     public ProducerRegistrationInfo(Collection<ProducerEiTypeRegistrationInfo> types, String jobCreationCallbackUrl,
index e64ea22..c2b8270 100644 (file)
@@ -27,9 +27,9 @@ import java.util.Map;
 import java.util.Vector;
 
 /**
- * Dynamic representation of all Rics in the system.
+ * A map, where each key can be bound to may values (where each value has an own
+ * ID)
  */
-
 public class MultiMap<T> {
 
     private final Map<String, Map<String, T>> map = new HashMap<>();
index cf34076..e92705e 100644 (file)
@@ -24,14 +24,13 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonParser;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 
 import org.junit.jupiter.api.AfterEach;
@@ -49,14 +48,12 @@ import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
 import org.oransc.enrichment.controllers.producer.ProducerConsts;
 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
+import org.oransc.enrichment.exceptions.ServiceException;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiJobs;
-import org.oransc.enrichment.repository.EiProducer;
 import org.oransc.enrichment.repository.EiProducers;
 import org.oransc.enrichment.repository.EiType;
 import org.oransc.enrichment.repository.EiTypes;
-import org.oransc.enrichment.repository.ImmutableEiJob;
-import org.oransc.enrichment.repository.ImmutableEiProducer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -86,6 +83,8 @@ import reactor.test.StepVerifier;
         "app.webclient.trust-store=./config/truststore.jks"})
 class ApplicationTest {
     private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class);
+    private final String EI_TYPE_ID = "typeId";
+    private final String EI_JOB_PROPERTY = "\"property1\"";
 
     @Autowired
     ApplicationContext context;
@@ -137,52 +136,55 @@ class ApplicationTest {
     }
 
     @Test
-    void getEiTypes() throws Exception {
-        addEiType("test");
+    void testGetEiTypes() throws Exception {
+        putEiProducerWithOneType("test");
         String url = ConsumerConsts.API_ROOT + "/eitypes";
         String rsp = restClient().get(url).block();
         assertThat(rsp).isEqualTo("[\"test\"]");
     }
 
     @Test
-    void getEiType() throws Exception {
-        addEiType("test");
+    void testGetEiType() throws Exception {
+        putEiProducerWithOneType("test");
         String url = ConsumerConsts.API_ROOT + "/eitypes/test";
         String rsp = restClient().get(url).block();
         assertThat(rsp).contains("job_data_schema");
     }
 
     @Test
-    void getEiTypeNotFound() throws Exception {
+    void testGetEiTypeNotFound() throws Exception {
         String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk");
     }
 
     @Test
-    void getEiJobsIds() throws Exception {
-        addEiJob("typeId", "jobId");
+    void testGetEiJobsIds() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs";
         String rsp = restClient().get(url).block();
         assertThat(rsp).isEqualTo("[\"jobId\"]");
     }
 
     @Test
-    void getEiJobTypeNotFound() throws Exception {
+    void testGetEiJobTypeNotFound() throws Exception {
         String url = ConsumerConsts.API_ROOT + "/eitypes/junk/eijobs";
         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk");
     }
 
     @Test
-    void getEiJob() throws Exception {
-        addEiJob("typeId", "jobId");
+    void testGetEiJob() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
         String rsp = restClient().get(url).block();
         assertThat(rsp).contains("job_data");
     }
 
     @Test
-    void getEiJobStatus() throws Exception {
-        addEiJob("typeId", "jobId");
+    void testGetEiJobStatus() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId/status";
         String rsp = restClient().get(url).block();
         assertThat(rsp).contains("ENABLED");
@@ -191,8 +193,9 @@ class ApplicationTest {
     // Status TBD
 
     @Test
-    void deleteEiJob() throws Exception {
-        addEiJob("typeId", "jobId");
+    void testDeleteEiJob() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         assertThat(this.eiJobs.size()).isEqualTo(1);
         String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
         restClient().delete(url).block();
@@ -204,8 +207,8 @@ class ApplicationTest {
     }
 
     @Test
-    void putEiJob() throws Exception {
-        addEiType("typeId");
+    void testPutEiJob() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
 
         String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
         String body = gson.toJson(eiJobInfo());
@@ -225,8 +228,21 @@ class ApplicationTest {
     }
 
     @Test
-    void getEiProducerTypes() throws Exception {
-        this.addEiJob("typeId", "jobId");
+    void testPutEiJob_jsonSchemavalidationError() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+
+        String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
+        // The element with name "property1" is mandatory in the schema
+        ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo(jsonObject("{ \"XXstring\" : \"value\" }"), "owner");
+        String body = gson.toJson(jobInfo);
+
+        testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Json validation failure");
+    }
+
+    @Test
+    void testGetEiProducerTypes() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         String url = ProducerConsts.API_ROOT + "/eitypes";
 
         ResponseEntity<String> resp = restClient().getForEntity(url).block();
@@ -234,31 +250,53 @@ class ApplicationTest {
     }
 
     @Test
-    void putEiProducer() throws Exception {
+    void testPutEiProducer() throws Exception {
         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
-        String body = gson.toJson(producerEiRegistratioInfo());
+        String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
 
         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
 
         assertThat(this.eiTypes.size()).isEqualTo(1);
-        EiType type = this.eiTypes.getType("typeId");
+        EiType type = this.eiTypes.getType(EI_TYPE_ID);
         assertThat(type.getProducerIds().contains("eiProducerId")).isTrue();
         assertThat(this.eiProducers.size()).isEqualTo(1);
-        assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId().equals("typeId")).isTrue();
+        assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId().equals(EI_TYPE_ID))
+            .isTrue();
 
         resp = restClient().putForEntity(url, body).block();
         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+        resp = restClient().getForEntity(url).block();
+        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
+        assertThat(resp.getBody()).isEqualTo(body);
     }
 
     @Test
-    void putEiProducerExistingJob() throws Exception {
-        this.addEiJob("typeId", "jobId");
+    void testPutEiProducerExistingJob() throws Exception {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId");
         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
-        String body = gson.toJson(producerEiRegistratioInfo());
+        String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
+        restClient().putForEntity(url, body).block();
 
-        ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
-        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+        ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
+        await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
+        ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
+        assertThat(request.id).isEqualTo("jobId");
+    }
+
+    @Test
+    void testPutProducerAndEiJob() throws Exception {
+        String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
+        String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
+        restClient().putForEntity(url, body).block();
+        assertThat(this.eiTypes.size()).isEqualTo(1);
+        this.eiTypes.getType(EI_TYPE_ID);
+
+        url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
+        body = gson.toJson(eiJobInfo());
+        restClient().putForEntity(url, body).block();
 
         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
@@ -267,13 +305,14 @@ class ApplicationTest {
     }
 
     @Test
-    void getEiJobsForProducer() {
-        this.addEiJob("typeId", "jobId1");
-        this.addEiJob("typeId", "jobId2");
+    void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
+        putEiProducerWithOneType(EI_TYPE_ID);
+        putEiJob(EI_TYPE_ID, "jobId1");
+        putEiJob(EI_TYPE_ID, "jobId2");
 
         // PUT a consumer
         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
-        String body = gson.toJson(producerEiRegistratioInfo());
+        String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
         restClient().putForEntity(url, body).block();
 
         url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
@@ -281,77 +320,95 @@ class ApplicationTest {
         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
 
         ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
-        assertThat(parsedResp[0].typeId).isEqualTo("typeId");
-        assertThat(parsedResp[1].typeId).isEqualTo("typeId");
+        assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
+        assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
     }
 
     @Test
-    void deleteEiProducer() throws Exception {
+    void testDeleteEiProducer() throws Exception {
         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
         String url2 = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId2";
-        String body = gson.toJson(producerEiRegistratioInfo());
+        String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
         restClient().putForEntity(url, body).block();
         restClient().putForEntity(url2, body).block();
         assertThat(this.eiProducers.size()).isEqualTo(2);
-        EiType type = this.eiTypes.getType("typeId");
+        EiType type = this.eiTypes.getType(EI_TYPE_ID);
         assertThat(type.getProducerIds().contains("eiProducerId")).isTrue();
         assertThat(type.getProducerIds().contains("eiProducerId2")).isTrue();
 
         restClient().deleteForEntity(url).block();
         assertThat(this.eiProducers.size()).isEqualTo(1);
-        assertThat(this.eiTypes.getType("typeId").getProducerIds().contains("eiProducerId")).isFalse();
+        assertThat(this.eiTypes.getType(EI_TYPE_ID).getProducerIds().contains("eiProducerId")).isFalse();
 
         restClient().deleteForEntity(url2).block();
         assertThat(this.eiProducers.size()).isEqualTo(0);
         assertThat(this.eiTypes.size()).isEqualTo(0);
     }
 
-    ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo() {
-        return new ProducerEiTypeRegistrationInfo(jsonObject(), "typeId");
+    ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
+        throws JsonMappingException, JsonProcessingException {
+        return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
     }
 
-    ProducerRegistrationInfo producerEiRegistratioInfo() {
+    ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
+        throws JsonMappingException, JsonProcessingException {
         Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
-        types.add(producerEiTypeRegistrationInfo());
+        types.add(producerEiTypeRegistrationInfo(typeId));
         return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
             baseUrl() + ProducerSimulatorController.JOB_DELETED_URL);
     }
 
-    ConsumerEiJobInfo eiJobInfo() {
+    ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
         return new ConsumerEiJobInfo(jsonObject(), "owner");
     }
 
-    JsonObject jsonObject() {
-        JsonObject jsonObj = new JsonObject();
-        JsonElement e = new JsonPrimitive(111);
-        jsonObj.add("param", e);
-        return jsonObj;
+    Object jsonObject(String json) {
+        try {
+            return JsonParser.parseString(json).getAsJsonObject();
+        } catch (Exception e) {
+            throw new NullPointerException(e.toString());
+        }
     }
 
-    private EiJob addEiJob(String typeId, String jobId) {
-        EiType type = addEiType(typeId);
-        EiJob job = ImmutableEiJob.builder() //
-            .id(jobId) //
-            .type(type) //
-            .owner("owner") //
-            .jobData(jsonObject()) //
-            .build();
-        this.eiJobs.put(job);
-        return job;
-    }
-
-    private EiType addEiType(String typeId) {
-        EiType eiType = new EiType(typeId, jsonObject());
-        this.eiTypes.put(eiType); //
-        EiProducer producer = ImmutableEiProducer.builder() //
-            .id("producerId") //
-            .eiTypes(Arrays.asList(eiType)) //
-            .jobCreationCallbackUrl(baseUrl() + ProducerSimulatorController.JOB_CREATED_URL) //
-            .jobDeletionCallbackUrl(baseUrl() + ProducerSimulatorController.JOB_DELETED_URL) //
-            .build();
-        this.eiProducers.put(producer);
-        eiType.addProducer(producer);
-        return eiType;
+    Object jsonSchemaObject() {
+        // a json schema with one mandatory property named "string"
+        String schemaStr = "{" //
+            + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
+            + "\"type\": \"object\"," //
+            + "\"properties\": {" //
+            + EI_JOB_PROPERTY + " : {" //
+            + "    \"type\": \"string\"" //
+            + "  }" //
+            + "}," //
+            + "\"required\": [" //
+            + EI_JOB_PROPERTY //
+            + "]" //
+            + "}"; //
+        return jsonObject(schemaStr);
+    }
+
+    Object jsonObject() {
+        return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
+    }
+
+    private EiJob putEiJob(String eiTypeId, String jobId)
+        throws JsonMappingException, JsonProcessingException, ServiceException {
+
+        String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/" + jobId;
+        String body = gson.toJson(eiJobInfo());
+        restClient().putForEntity(url, body).block();
+
+        return this.eiJobs.getJob(jobId);
+    }
+
+    private EiType putEiProducerWithOneType(String eiTypeId)
+        throws JsonMappingException, JsonProcessingException, ServiceException {
+        String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
+        String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
+
+        restClient().putForEntity(url, body).block();
+        assertThat(this.eiTypes.size()).isEqualTo(1);
+        return this.eiTypes.getType(eiTypeId);
     }
 
     private String baseUrl() {
index 307d409..b6b8bc3 100644 (file)
@@ -72,7 +72,7 @@ public class ProducerSimulatorController {
     private TestResults testResults = new TestResults();
 
     @PostMapping(path = JOB_CREATED_URL, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Callback for job creation", notes = "")
+    @ApiOperation(value = "Callback for EI job creation", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "OK", response = void.class)}//
@@ -93,7 +93,7 @@ public class ProducerSimulatorController {
     }
 
     @PostMapping(path = JOB_DELETED_URL, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiOperation(value = "Callback for job deletion", notes = "")
+    @ApiOperation(value = "Callback for EI job deletion", notes = "")
     @ApiResponses(
         value = { //
             @ApiResponse(code = 200, message = "OK", response = void.class)}//