Enrichment Coordination Service 07/4707/3
authorPatrikBuhr <patrik.buhr@est.tech>
Fri, 11 Sep 2020 14:50:37 +0000 (16:50 +0200)
committerPatrikBuhr <patrik.buhr@est.tech>
Thu, 17 Sep 2020 09:31:24 +0000 (11:31 +0200)
First part. Consumer API defined.

Change-Id: I0841cba636c2e79ed0e54978641434e83aff77e5
Issue-ID: NONRTRIC-173
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
25 files changed:
enrichment-coordinator-service/config/README [new file with mode: 0644]
enrichment-coordinator-service/config/application.yaml [new file with mode: 0644]
enrichment-coordinator-service/config/keystore.jks [new file with mode: 0644]
enrichment-coordinator-service/config/truststore.jks [new file with mode: 0644]
enrichment-coordinator-service/docs/api.yaml [new file with mode: 0644]
enrichment-coordinator-service/eclipse-formatter.xml [new file with mode: 0644]
enrichment-coordinator-service/pom.xml [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/Application.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/AsyncRestClient.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/ApplicationConfig.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/WebClientConfig.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiTypeInfo.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/exceptions/ServiceException.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java [new file with mode: 0644]
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java [new file with mode: 0644]

diff --git a/enrichment-coordinator-service/config/README b/enrichment-coordinator-service/config/README
new file mode 100644 (file)
index 0000000..140927f
--- /dev/null
@@ -0,0 +1,41 @@
+The keystore.jks and truststore.jks files are created by using the following commands (note that this is an example):
+
+1) Create a CA certificate and a private key:
+
+openssl genrsa -des3 -out CA-key.pem 2048
+openssl req -new -key CA-key.pem -x509 -days 1000 -out CA-cert.pem
+
+2) Create a keystore with a private key entry that is signed by the CA:
+
+keytool -genkeypair -alias policy_agent -keyalg RSA -keysize 2048 -keystore keystore.jks -validity 3650 -storepass policy_agent
+keytool -certreq -alias policy_agent -file request.csr -keystore keystore.jks -ext san=dns:your.domain.com -storepass policy_agent
+openssl x509 -req -days 365 -in request.csr -CA CA-cert.pem -CAkey CA-key.pem -CAcreateserial -out ca_signed-cert.pem
+keytool -importcert -alias ca_cert -file CA-cert.pem -keystore keystore.jks -trustcacerts -storepass policy_agent
+keytool -importcert -alias policy_agent -file ca_signed-cert.pem -keystore keystore.jks -trustcacerts -storepass policy_agent
+
+
+3) Create a trust store containing the CA cert (to trust all certs signed by the CA):
+
+keytool -genkeypair -alias not_used -keyalg RSA -keysize 2048 -keystore truststore.jks -validity 3650 -storepass policy_agent
+keytool -importcert -alias ca_cert -file CA-cert.pem -keystore truststore.jks -trustcacerts -storepass policy_agent
+
+
+4) Command for listing of the contents of jks files, examples:
+keytool -list -v -keystore keystore.jks -storepass policy_agent
+keytool -list -v -keystore truststore.jks -storepass policy_agent
+
+## License
+
+Copyright (C) 2020 Nordix Foundation. 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.
+
diff --git a/enrichment-coordinator-service/config/application.yaml b/enrichment-coordinator-service/config/application.yaml
new file mode 100644 (file)
index 0000000..6279a3e
--- /dev/null
@@ -0,0 +1,37 @@
+spring:
+  profiles:
+    active: prod
+  main:
+    allow-bean-definition-overriding: true
+  aop:
+    auto: false
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "loggers,logfile,health,info,metrics,threaddump,heapdump"
+
+logging:
+  level:
+    ROOT: ERROR
+    org.springframework: ERROR
+    org.springframework.data: ERROR
+    org.springframework.web.reactive.function.client.ExchangeFunctions: ERROR
+    org.oransc.policyagent: INFO
+  file: /var/log/policy-agent/application.log
+server:
+   port : 8433
+   http-port: 8081
+   ssl:
+      key-store-type: JKS
+      key-store-password: policy_agent
+      key-store: /opt/app/enrichment-coordinator-service/etc/cert/keystore.jks
+      key-password: policy_agent
+      key-alias: policy_agent
+app:
+  filepath: /opt/app/enrichment-coordinator-service/data/application_configuration.json
+  webclient:
+    trust-store-used: false
+    trust-store-password: policy_agent
+    trust-store: /opt/app/enrichment-coordinator-service/etc/cert/truststore.jks
+
diff --git a/enrichment-coordinator-service/config/keystore.jks b/enrichment-coordinator-service/config/keystore.jks
new file mode 100644 (file)
index 0000000..122997a
Binary files /dev/null and b/enrichment-coordinator-service/config/keystore.jks differ
diff --git a/enrichment-coordinator-service/config/truststore.jks b/enrichment-coordinator-service/config/truststore.jks
new file mode 100644 (file)
index 0000000..60d6288
Binary files /dev/null and b/enrichment-coordinator-service/config/truststore.jks differ
diff --git a/enrichment-coordinator-service/docs/api.yaml b/enrichment-coordinator-service/docs/api.yaml
new file mode 100644 (file)
index 0000000..a5c07e7
--- /dev/null
@@ -0,0 +1,300 @@
+swagger: '2.0'
+info:
+  description: This page lists all the rest apis for the service.
+  version: '1.0'
+  title: Enrichment Data service
+host: 'localhost:8081'
+basePath: /
+tags:
+  - name: A1-E Enrichment Data Consumer API
+    description: Consumer Controller
+paths:
+  /A1-EI/v1/eitypes:
+    get:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Query EI type identifiers
+      description: DETAILS TBD
+      operationId: getEiTypeIdentifiersUsingGET
+      produces:
+        - application/json
+      responses:
+        '200':
+          description: EI type identifiers
+          schema:
+            type: array
+            items:
+              type: string
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Not Found
+      deprecated: false
+  '/A1-EI/v1/eitypes/{eiTypeId}':
+    get:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Definitions for an individual EI Type
+      description: Query EI type
+      operationId: getEiTypeUsingGET
+      produces:
+        - application/json
+      parameters:
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: EI type
+          schema:
+            $ref: '#/definitions/ei_type_info'
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+  '/A1-EI/v1/eitypes/{eiTypeId}/eijobs':
+    get:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Query EI job identifiers
+      description: Returns the identifiers for an EI Type
+      operationId: getEiJobIdsUsingGET
+      produces:
+        - application/json
+      parameters:
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+        - in: body
+          name: owner
+          description: identifies the owner of the job
+          required: false
+          schema:
+            type: string
+      responses:
+        '200':
+          description: EI type
+          schema:
+            type: array
+            items:
+              type: string
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+  '/A1-EI/v1/eitypes/{eiTypeId}/eijobs/{eiJobId}':
+    get:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Individual EI Job
+      operationId: getIndividualEiJobUsingGET
+      produces:
+        - application/json
+      parameters:
+        - name: eiJobId
+          in: path
+          description: eiJobId
+          required: true
+          type: string
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: EI Job
+          schema:
+            $ref: '#/definitions/ei_job_info'
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type or job is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+    put:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Individual EI Job
+      description: Create or update an EI Job
+      operationId: putIndividualEiJobUsingPUT
+      consumes:
+        - application/json
+      produces:
+        - application/json
+      parameters:
+        - name: eiJobId
+          in: path
+          description: eiJobId
+          required: true
+          type: string
+        - in: body
+          name: eiJobInfo
+          description: eiJobInfo
+          required: true
+          schema:
+            $ref: '#/definitions/ei_job_info'
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: Job updated
+          schema:
+            type: object
+        '201':
+          description: Job created
+          schema:
+            type: object
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+    delete:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: Individual EI Job
+      description: Delete an EI job
+      operationId: deleteIndividualEiJobUsingDELETE
+      produces:
+        - application/json
+      parameters:
+        - name: eiJobId
+          in: path
+          description: eiJobId
+          required: true
+          type: string
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: Not used
+          schema:
+            type: object
+        '204':
+          description: Job deleted
+          schema:
+            type: object
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type or job is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+  '/A1-EI/v1/eitypes/{eiTypeId}/eijobs/{eiJobId}/status':
+    get:
+      tags:
+        - A1-E Enrichment Data Consumer API
+      summary: EI Job status
+      operationId: getEiJobStatusUsingGET
+      produces:
+        - application/json
+      parameters:
+        - name: eiJobId
+          in: path
+          description: eiJobId
+          required: true
+          type: string
+        - name: eiTypeId
+          in: path
+          description: eiTypeId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: EI Job status
+          schema:
+            $ref: '#/definitions/ei_job_status'
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information type or job is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
+definitions:
+  ei_job_info:
+    type: object
+    properties:
+      job_data:
+        type: object
+        description: EI Type specific job data
+      owner:
+        type: string
+        description: Identity of the owner of the job
+      result_target:
+        type: string
+        description: the deliver information for the EI. This is typically a URL.
+    title: ei_job_info
+    description: Information for a Enrichment Information Job
+  ei_job_status:
+    type: object
+    properties:
+      operational_state:
+        type: string
+        description: |-
+          Operational state, values: 
+          ENABLED: TBD 
+          DISABLED: TBD.
+        enum:
+          - ENABLED
+          - DISABLED
+    title: ei_job_status
+    description: Status for an EI Job
+  ei_type_info:
+    type: object
+    properties:
+      job_data_schema:
+        type: object
+        description: Json schema for the job data
+    title: ei_type_info
+    description: Information for an EI type
+  error_information:
+    type: object
+    properties:
+      detail:
+        type: string
+        example: EI job type not found
+        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. '
+    title: error_information
+    description: 'Problem as defined in https://tools.ietf.org/html/rfc7807'
+
diff --git a/enrichment-coordinator-service/eclipse-formatter.xml b/enrichment-coordinator-service/eclipse-formatter.xml
new file mode 100644 (file)
index 0000000..c8cca2e
--- /dev/null
@@ -0,0 +1,314 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+  -->
+<profiles version="13">
+<profile kind="CodeFormatterProfile" name="java-formatter" version="12">
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+</profile>
+</profiles>
diff --git a/enrichment-coordinator-service/pom.xml b/enrichment-coordinator-service/pom.xml
new file mode 100644 (file)
index 0000000..e1884fc
--- /dev/null
@@ -0,0 +1,382 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+* ========================LICENSE_START=================================
+* O-RAN-SC
+* %%
+* Copyright (C) 2019 Nordix Foundation
+* %%
+* 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.
+* ========================LICENSE_END===================================
+-->
+<project
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.2.7.RELEASE</version>
+        <relativePath />
+    </parent>
+    <groupId>org.o-ran-sc.nonrtric</groupId>
+    <artifactId>policy-agent</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+    <repositories>
+        <repository>
+            <id>onap-releases</id>
+            <name>onap-releases</name>
+            <url>https://nexus.onap.org/content/repositories/releases/</url>
+        </repository>
+    </repositories>
+    <properties>
+        <java.version>11</java.version>
+        <springfox.version>2.9.2</springfox.version>
+        <immutable.version>2.8.2</immutable.version>
+        <sdk.version>1.1.6</sdk.version>
+        <swagger.version>2.0.0</swagger.version>
+        <json.version>20190722</json.version>
+        <commons-net.version>3.6</commons-net.version>
+        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
+        <formatter-maven-plugin.version>2.8.1</formatter-maven-plugin.version>
+        <spotless-maven-plugin.version>1.18.0</spotless-maven-plugin.version>
+        <docker-maven-plugin>0.30.0</docker-maven-plugin>
+        <version.dmaap>1.1.11</version.dmaap>
+        <javax.ws.rs-api.version>2.1.1</javax.ws.rs-api.version>
+        <sonar-maven-plugin.version>3.7.0.1746</sonar-maven-plugin.version>
+        <jacoco-maven-plugin.version>0.8.5</jacoco-maven-plugin.version>
+        <exec.skip>true</exec.skip>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-jaxrs2</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.immutables</groupId>
+            <artifactId>value</artifactId>
+            <version>${immutable.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.immutables</groupId>
+            <artifactId>gson</artifactId>
+            <version>${immutable.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>${json.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId>
+            <version>${commons-net.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.dcaegen2.services.sdk.rest.services</groupId>
+            <artifactId>cbs-client</artifactId>
+            <version>${sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.dmaap.messagerouter.dmaapclient</groupId>
+            <artifactId>dmaapClient</artifactId>
+            <version>${version.dmaap}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <version>${javax.ws.rs-api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+        </dependency>
+        <!-- Actuator dependencies -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <!--REQUIRED TO GENERATE DOCUMENTATION -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${springfox.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${springfox.version}</version>
+        </dependency>
+        <!-- TEST -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>net.revelc.code.formatter</groupId>
+                <artifactId>formatter-maven-plugin</artifactId>
+                <version>${formatter-maven-plugin.version}</version>
+                <configuration>
+                    <configFile>${project.basedir}/eclipse-formatter.xml</configFile>
+                </configuration>
+                <!-- https://code.revelc.net/formatter-maven-plugin/ use mvn formatter:format
+                                       spotless:apply process-sources -->
+            </plugin>
+            <plugin>
+                <groupId>com.diffplug.spotless</groupId>
+                <artifactId>spotless-maven-plugin</artifactId>
+                <version>${spotless-maven-plugin.version}</version>
+                <configuration>
+                    <java>
+                        <removeUnusedImports />
+                        <importOrder>
+                            <order>com,java,javax,org</order>
+                        </importOrder>
+                    </java>
+                </configuration>
+                <!-- https://github.com/diffplug/spotless/tree/master/plugin-maven use
+                                       mvn spotless:apply to rewrite source files use mvn spotless:check to validate
+                                       source files -->
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skipTests>false</skipTests>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>add-source</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>${project.build.directory}/generated-sources/annotations/</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <version>${jacoco-maven-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>default-prepare-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>default-report</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>io.fabric8</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>${docker-maven-plugin}</version>
+                <inherited>false</inherited>
+                <executions>
+                    <execution>
+                        <id>generate-policy-agent-image</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                        <configuration>
+                            <pullRegistry>${env.CONTAINER_PULL_REGISTRY}</pullRegistry>
+                            <images>
+                                <image>
+                                    <name>o-ran-sc/nonrtric-policy-agent:${project.version}</name>
+                                    <build>
+                                        <cleanup>try</cleanup>
+                                        <contextDir>${basedir}</contextDir>
+                                        <dockerFile>Dockerfile</dockerFile>
+                                        <args>
+                                            <JAR>${project.build.finalName}.jar</JAR>
+                                        </args>
+                                        <tags>
+                                            <tag>${project.version}</tag>
+                                        </tags>
+                                    </build>
+                                </image>
+                            </images>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>push-policy-agent-image</id>
+                        <goals>
+                            <goal>build</goal>
+                            <goal>push</goal>
+                        </goals>
+                        <configuration>
+                            <pullRegistry>${env.CONTAINER_PULL_REGISTRY}</pullRegistry>
+                            <pushRegistry>${env.CONTAINER_PUSH_REGISTRY}</pushRegistry>
+                            <images>
+                                <image>
+                                    <name>o-ran-sc/nonrtric-policy-agent:${project.version}</name>
+                                    <build>
+                                        <contextDir>${basedir}</contextDir>
+                                        <dockerFile>Dockerfile</dockerFile>
+                                        <args>
+                                            <JAR>${project.build.finalName}.jar</JAR>
+                                        </args>
+                                        <tags>
+                                            <tag>${project.version}</tag>
+                                            <tag>latest</tag>
+                                        </tags>
+                                    </build>
+                                </image>
+                            </images>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- support sonar in multi-module project -->
+            <plugin>
+                <groupId>org.sonarsource.scanner.maven</groupId>
+                <artifactId>sonar-maven-plugin</artifactId>
+                <version>${sonar-maven-plugin.version}</version>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>run-test-script</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <executable>bash</executable>
+                    <arguments>
+                        <argument>run_test.sh</argument>
+                    </arguments>
+                    <workingDirectory>../test/jenkins/</workingDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <issueManagement>
+        <system>JIRA</system>
+        <url>https://jira.o-ran-sc.org/</url>
+    </issueManagement>
+</project>
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/Application.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/Application.java
new file mode 100644 (file)
index 0000000..3c0156c
--- /dev/null
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class);
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java
new file mode 100644 (file)
index 0000000..7b868f5
--- /dev/null
@@ -0,0 +1,80 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.apache.catalina.connector.Connector;
+import org.oransc.enrichment.configuration.ApplicationConfig;
+import org.oransc.enrichment.repository.EiJobs;
+import org.oransc.enrichment.repository.EiTypes;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+class BeanFactory {
+
+    @Value("${server.http-port}")
+    private int httpPort = 0;
+
+    private final ApplicationConfig applicationConfig = new ApplicationConfig();
+
+    @Bean
+    public ObjectMapper mapper() {
+        return new ObjectMapper();
+    }
+
+    @Bean
+    public ServletWebServerFactory servletContainer() {
+        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
+        if (httpPort > 0) {
+            tomcat.addAdditionalTomcatConnectors(getHttpConnector(httpPort));
+        }
+        return tomcat;
+    }
+
+    @Bean
+    public EiJobs eiJobs() {
+        return new EiJobs();
+    }
+
+    @Bean
+    public EiTypes eiTypes() {
+        return new EiTypes();
+    }
+
+    @Bean
+    public ApplicationConfig getApplicationConfig() {
+        return this.applicationConfig;
+    }
+
+    private static Connector getHttpConnector(int httpPort) {
+        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
+        connector.setScheme("http");
+        connector.setPort(httpPort);
+        connector.setSecure(false);
+        return connector;
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java
new file mode 100644 (file)
index 0000000..07dbefd
--- /dev/null
@@ -0,0 +1,95 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment;
+
+import com.fasterxml.classmate.TypeResolver;
+import com.google.common.base.Predicates;
+
+import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Swagger configuration class that uses swagger2 documentation type and scans
+ * all the controllers. To access the swagger gui go to
+ * http://ip:port/swagger-ui.html
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig extends WebMvcConfigurationSupport {
+
+    static final String API_TITLE = "Enrichment Data service";
+    static final String DESCRIPTION = "This page lists all the rest apis for the service.";
+    static final String VERSION = "1.0";
+    @SuppressWarnings("squid:S1075") // Refactor your code to get this URI from a customizable parameter.
+    static final String RESOURCES_PATH = "classpath:/META-INF/resources/";
+    static final String WEBJARS_PATH = RESOURCES_PATH + "webjars/";
+    static final String SWAGGER_UI = "swagger-ui.html";
+    static final String WEBJARS = "/webjars/**";
+
+    /**
+     * Gets the API info.
+     *
+     * @return the API info.
+     */
+    @Bean
+    public Docket api(TypeResolver resolver) {
+        return new Docket(DocumentationType.SWAGGER_2) //
+            .apiInfo(apiInfo()) //
+            .additionalModels(resolver.resolve(ConsumerEiJobInfo.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
+            .build();
+    }
+
+    private static ApiInfo apiInfo() {
+        return new ApiInfoBuilder() //
+            .title(API_TITLE) //
+            .description(DESCRIPTION) //
+            .version(VERSION) //
+            .build();
+    }
+
+    @Override
+    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler(SWAGGER_UI) //
+            .addResourceLocations(RESOURCES_PATH);
+
+        registry.addResourceHandler(WEBJARS) //
+            .addResourceLocations(WEBJARS_PATH);
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/AsyncRestClient.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/AsyncRestClient.java
new file mode 100644 (file)
index 0000000..2782f94
--- /dev/null
@@ -0,0 +1,334 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.clients;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.handler.timeout.WriteTimeoutHandler;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import javax.net.ssl.KeyManagerFactory;
+
+import org.oransc.enrichment.configuration.WebClientConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.reactive.function.client.ExchangeStrategies;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+import reactor.netty.tcp.TcpClient;
+
+/**
+ * Generic reactive REST client.
+ */
+public class AsyncRestClient {
+    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+    private WebClient webClient = null;
+    private final String baseUrl;
+    private static final AtomicInteger sequenceNumber = new AtomicInteger();
+    private final WebClientConfig clientConfig;
+    static KeyStore clientTrustStore = null;
+    private boolean sslEnabled = true;
+
+    public AsyncRestClient(String baseUrl) {
+        this(baseUrl, null);
+        this.sslEnabled = false;
+    }
+
+    public AsyncRestClient(String baseUrl, WebClientConfig config) {
+        this.baseUrl = baseUrl;
+        this.clientConfig = config;
+    }
+
+    public Mono<ResponseEntity<String>> postForEntity(String uri, @Nullable String body) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} POST uri = '{}{}''", traceTag, baseUrl, uri);
+        logger.trace("{} POST body: {}", traceTag, body);
+        Mono<String> bodyProducer = body != null ? Mono.just(body) : Mono.empty();
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.post() //
+                    .uri(uri) //
+                    .contentType(MediaType.APPLICATION_JSON) //
+                    .body(bodyProducer, String.class);
+                return retrieve(traceTag, request);
+            });
+    }
+
+    public Mono<String> post(String uri, @Nullable String body) {
+        return postForEntity(uri, body) //
+            .flatMap(this::toBody);
+    }
+
+    public Mono<String> postWithAuthHeader(String uri, String body, String username, String password) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} POST (auth) uri = '{}{}''", traceTag, baseUrl, uri);
+        logger.trace("{} POST body: {}", traceTag, body);
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.post() //
+                    .uri(uri) //
+                    .headers(headers -> headers.setBasicAuth(username, password)) //
+                    .contentType(MediaType.APPLICATION_JSON) //
+                    .bodyValue(body);
+                return retrieve(traceTag, request) //
+                    .flatMap(this::toBody);
+            });
+    }
+
+    public Mono<ResponseEntity<String>> putForEntity(String uri, String body) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} PUT uri = '{}{}''", traceTag, baseUrl, uri);
+        logger.trace("{} PUT body: {}", traceTag, body);
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.put() //
+                    .uri(uri) //
+                    .contentType(MediaType.APPLICATION_JSON) //
+                    .bodyValue(body);
+                return retrieve(traceTag, request);
+            });
+    }
+
+    public Mono<ResponseEntity<String>> putForEntity(String uri) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} PUT uri = '{}{}''", traceTag, baseUrl, uri);
+        logger.trace("{} PUT body: <empty>", traceTag);
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.put() //
+                    .uri(uri);
+                return retrieve(traceTag, request);
+            });
+    }
+
+    public Mono<String> put(String uri, String body) {
+        return putForEntity(uri, body) //
+            .flatMap(this::toBody);
+    }
+
+    public Mono<ResponseEntity<String>> getForEntity(String uri) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} GET uri = '{}{}''", traceTag, baseUrl, uri);
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.get().uri(uri);
+                return retrieve(traceTag, request);
+            });
+    }
+
+    public Mono<String> get(String uri) {
+        return getForEntity(uri) //
+            .flatMap(this::toBody);
+    }
+
+    public Mono<ResponseEntity<String>> deleteForEntity(String uri) {
+        Object traceTag = createTraceTag();
+        logger.debug("{} DELETE uri = '{}{}''", traceTag, baseUrl, uri);
+        return getWebClient() //
+            .flatMap(client -> {
+                RequestHeadersSpec<?> request = client.delete().uri(uri);
+                return retrieve(traceTag, request);
+            });
+    }
+
+    public Mono<String> delete(String uri) {
+        return deleteForEntity(uri) //
+            .flatMap(this::toBody);
+    }
+
+    private Mono<ResponseEntity<String>> retrieve(Object traceTag, RequestHeadersSpec<?> request) {
+        final Class<String> clazz = String.class;
+        return request.retrieve() //
+            .toEntity(clazz) //
+            .doOnNext(entity -> logger.trace("{} Received: {}", traceTag, entity.getBody())) //
+            .doOnError(throwable -> onHttpError(traceTag, throwable));
+    }
+
+    private static Object createTraceTag() {
+        return sequenceNumber.incrementAndGet();
+    }
+
+    private void onHttpError(Object traceTag, Throwable t) {
+        if (t instanceof WebClientResponseException) {
+            WebClientResponseException exception = (WebClientResponseException) t;
+            logger.debug("{} HTTP error status = '{}', body '{}'", traceTag, exception.getStatusCode(),
+                exception.getResponseBodyAsString());
+        } else {
+            logger.debug("{} HTTP error", traceTag, t);
+        }
+    }
+
+    private Mono<String> toBody(ResponseEntity<String> entity) {
+        if (entity.getBody() == null) {
+            return Mono.just("");
+        } else {
+            return Mono.just(entity.getBody());
+        }
+    }
+
+    private boolean isCertificateEntry(KeyStore trustStore, String alias) {
+        try {
+            return trustStore.isCertificateEntry(alias);
+        } catch (KeyStoreException e) {
+            logger.error("Error reading truststore {}", e.getMessage());
+            return false;
+        }
+    }
+
+    private Certificate getCertificate(KeyStore trustStore, String alias) {
+        try {
+            return trustStore.getCertificate(alias);
+        } catch (KeyStoreException e) {
+            logger.error("Error reading truststore {}", e.getMessage());
+            return null;
+        }
+    }
+
+    private static synchronized KeyStore getTrustStore(String trustStorePath, String trustStorePass)
+        throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
+        if (clientTrustStore == null) {
+            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
+            store.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
+            clientTrustStore = store;
+        }
+        return clientTrustStore;
+    }
+
+    private SslContext createSslContextRejectingUntrustedPeers(String trustStorePath, String trustStorePass,
+        KeyManagerFactory keyManager)
+        throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
+
+        final KeyStore trustStore = getTrustStore(trustStorePath, trustStorePass);
+        List<Certificate> certificateList = Collections.list(trustStore.aliases()).stream() //
+            .filter(alias -> isCertificateEntry(trustStore, alias)) //
+            .map(alias -> getCertificate(trustStore, alias)) //
+            .collect(Collectors.toList());
+        final X509Certificate[] certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
+
+        return SslContextBuilder.forClient() //
+            .keyManager(keyManager) //
+            .trustManager(certificates) //
+            .build();
+    }
+
+    private SslContext createSslContext(KeyManagerFactory keyManager)
+        throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
+        if (this.clientConfig.isTrustStoreUsed()) {
+            return createSslContextRejectingUntrustedPeers(this.clientConfig.trustStore(),
+                this.clientConfig.trustStorePassword(), keyManager);
+        } else {
+            // Trust anyone
+            return SslContextBuilder.forClient() //
+                .keyManager(keyManager) //
+                .trustManager(InsecureTrustManagerFactory.INSTANCE) //
+                .build();
+        }
+    }
+
+    private TcpClient createTcpClientSecure(SslContext sslContext) {
+        return TcpClient.create(ConnectionProvider.newConnection()) //
+            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
+            .secure(c -> c.sslContext(sslContext)) //
+            .doOnConnected(connection -> {
+                connection.addHandlerLast(new ReadTimeoutHandler(30));
+                connection.addHandlerLast(new WriteTimeoutHandler(30));
+            });
+    }
+
+    private TcpClient createTcpClientInsecure() {
+        return TcpClient.create(ConnectionProvider.newConnection()) //
+            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
+            .doOnConnected(connection -> {
+                connection.addHandlerLast(new ReadTimeoutHandler(30));
+                connection.addHandlerLast(new WriteTimeoutHandler(30));
+            });
+    }
+
+    private WebClient createWebClient(String baseUrl, TcpClient tcpClient) {
+        HttpClient httpClient = HttpClient.from(tcpClient);
+        ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
+        ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() //
+            .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)) //
+            .build();
+        return WebClient.builder() //
+            .clientConnector(connector) //
+            .baseUrl(baseUrl) //
+            .exchangeStrategies(exchangeStrategies) //
+            .build();
+    }
+
+    private Mono<WebClient> getWebClient() {
+        if (this.webClient == null) {
+            try {
+                if (this.sslEnabled) {
+                    final KeyManagerFactory keyManager =
+                        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+                    final KeyStore keyStore = KeyStore.getInstance(this.clientConfig.keyStoreType());
+                    final String keyStoreFile = this.clientConfig.keyStore();
+                    final String keyStorePassword = this.clientConfig.keyStorePassword();
+                    final String keyPassword = this.clientConfig.keyPassword();
+                    try (final InputStream inputStream = new FileInputStream(keyStoreFile)) {
+                        keyStore.load(inputStream, keyStorePassword.toCharArray());
+                    }
+                    keyManager.init(keyStore, keyPassword.toCharArray());
+                    SslContext sslContext = createSslContext(keyManager);
+                    TcpClient tcpClient = createTcpClientSecure(sslContext);
+                    this.webClient = createWebClient(this.baseUrl, tcpClient);
+                } else {
+                    TcpClient tcpClient = createTcpClientInsecure();
+                    this.webClient = createWebClient(this.baseUrl, tcpClient);
+                }
+            } catch (Exception e) {
+                logger.error("Could not create WebClient {}", e.getMessage());
+                return Mono.error(e);
+            }
+        }
+        return Mono.just(this.webClient);
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/ApplicationConfig.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/ApplicationConfig.java
new file mode 100644 (file)
index 0000000..225b83a
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2019-2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.configuration;
+
+import javax.validation.constraints.NotEmpty;
+
+import lombok.Getter;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@EnableConfigurationProperties
+@ConfigurationProperties()
+public class ApplicationConfig {
+    @NotEmpty
+    @Getter
+    @Value("${app.filepath}")
+    private String localConfigurationFilePath;
+
+    @Value("${server.ssl.key-store-type}")
+    private String sslKeyStoreType = "";
+
+    @Value("${server.ssl.key-store-password}")
+    private String sslKeyStorePassword = "";
+
+    @Value("${server.ssl.key-store}")
+    private String sslKeyStore = "";
+
+    @Value("${server.ssl.key-password}")
+    private String sslKeyPassword = "";
+
+    @Value("${app.webclient.trust-store-used}")
+    private boolean sslTrustStoreUsed = false;
+
+    @Value("${app.webclient.trust-store-password}")
+    private String sslTrustStorePassword = "";
+
+    @Value("${app.webclient.trust-store}")
+    private String sslTrustStore = "";
+
+    public WebClientConfig getWebClientConfig() {
+        return ImmutableWebClientConfig.builder() //
+            .keyStoreType(this.sslKeyStoreType) //
+            .keyStorePassword(this.sslKeyStorePassword) //
+            .keyStore(this.sslKeyStore) //
+            .keyPassword(this.sslKeyPassword) //
+            .isTrustStoreUsed(this.sslTrustStoreUsed) //
+            .trustStore(this.sslTrustStore) //
+            .trustStorePassword(this.sslTrustStorePassword) //
+            .build();
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/WebClientConfig.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/WebClientConfig.java
new file mode 100644 (file)
index 0000000..61d0f5a
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.configuration;
+
+import org.immutables.value.Value;
+
+@Value.Immutable
+@Value.Style(redactedMask = "####")
+public interface WebClientConfig {
+    public String keyStoreType();
+
+    @Value.Redacted
+    public String keyStorePassword();
+
+    public String keyStore();
+
+    @Value.Redacted
+    public String keyPassword();
+
+    public boolean isTrustStoreUsed();
+
+    @Value.Redacted
+    public String trustStorePassword();
+
+    public String trustStore();
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java
new file mode 100644 (file)
index 0000000..1df2df7
--- /dev/null
@@ -0,0 +1,106 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import reactor.core.publisher.Mono;
+
+public class ErrorResponse {
+    private static Gson gson = new GsonBuilder() //
+        .create(); //
+
+    // Returned as body for all failed REST calls
+    @ApiModel(value = "error_information", description = "Problem as defined in https://tools.ietf.org/html/rfc7807")
+    public static class ErrorInfo {
+        @SerializedName("type")
+        private String type = "about:blank";
+
+        @SerializedName("title")
+        private String title = null;
+
+        @SerializedName("status")
+        private final Integer status;
+
+        @SerializedName("detail")
+        private String detail = null;
+
+        @SerializedName("instance")
+        private String instance = null;
+
+        public ErrorInfo(String detail, Integer status) {
+            this.detail = detail;
+            this.status = status;
+        }
+
+        @ApiModelProperty(
+            example = "503",
+            value = "The HTTP status code generated by the origin server for this occurrence of the problem.")
+        public Integer getStatus() {
+            return status;
+        }
+
+        @ApiModelProperty(
+            example = "EI job type not found",
+            value = "A human-readable explanation specific to this occurrence of the problem.")
+        public String getDetail() {
+            return this.detail;
+        }
+
+    }
+
+    @ApiModelProperty(value = "message")
+    public final String message;
+
+    ErrorResponse(String message) {
+        this.message = message;
+    }
+
+    public static Mono<ResponseEntity<Object>> createMono(String text, HttpStatus code) {
+        return Mono.just(create(text, code));
+    }
+
+    public static Mono<ResponseEntity<Object>> createMono(Exception e, HttpStatus code) {
+        return createMono(e.toString(), code);
+    }
+
+    public static ResponseEntity<Object> create(String text, HttpStatus code) {
+        ErrorInfo p = new ErrorInfo(text, code.value());
+        String json = gson.toJson(p);
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_PROBLEM_JSON);
+        return new ResponseEntity<>(json, headers, code);
+    }
+
+    public static ResponseEntity<Object> create(Exception e, HttpStatus code) {
+        return create(e.toString(), code);
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java
new file mode 100644 (file)
index 0000000..d38d6dc
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.consumer;
+
+public class ConsumerConsts {
+
+    public static final String A1E_API_ROOT = "/A1-EI/v1";
+    public static final String CONSUMER_API_NAME = "A1-E Enrichment Data Consumer API";
+    public static final String OWNER_PARAM = "owner";
+    public static final String OWNER_PARAM_DESCRIPTION = "identifies the owner of the job";
+
+    private ConsumerConsts() {
+    }
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java
new file mode 100644 (file)
index 0000000..7dfaeca
--- /dev/null
@@ -0,0 +1,258 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2019-2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.consumer;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.repository.EiJob;
+import org.oransc.enrichment.repository.EiJobs;
+import org.oransc.enrichment.repository.EiType;
+import org.oransc.enrichment.repository.EiTypes;
+import org.oransc.enrichment.repository.ImmutableEiJob;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("ConsumerController")
+@Api(tags = {ConsumerConsts.CONSUMER_API_NAME})
+public class ConsumerController {
+
+    @Autowired
+    private EiJobs eiJobs;
+
+    @Autowired
+    private EiTypes eiTypes;
+
+    private static Gson gson = new GsonBuilder() //
+        .serializeNulls() //
+        .create(); //
+
+    @GetMapping(path = ConsumerConsts.A1E_API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(
+                code = 200,
+                message = "EI type identifiers",
+                response = String.class,
+                responseContainer = "List"), //
+        })
+    public ResponseEntity<Object> getEiTypeIdentifiers( //
+    ) {
+        List<String> result = new ArrayList<>();
+        for (EiType eiType : this.eiTypes.getAllEiTypes()) {
+            result.add(eiType.id());
+        }
+
+        return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
+    }
+
+    @GetMapping(path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Definitions for an individual EI Type", notes = "Query EI type")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "EI type", response = ConsumerEiTypeInfo.class), //
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> getEiType( //
+        @PathVariable("eiTypeId") String eiTypeId) {
+        try {
+            EiType t = this.eiTypes.getType(eiTypeId);
+            ConsumerEiTypeInfo info = toEiTypeInfo(t);
+            return new ResponseEntity<>(gson.toJson(info), HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @GetMapping(
+        path = ConsumerConsts.A1E_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")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(
+                code = 200,
+                message = "EI job identifiers",
+                response = String.class,
+                responseContainer = "List"), //
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> getEiJobIds( //
+        @PathVariable("eiTypeId") String eiTypeId, //
+        @ApiParam(
+            name = ConsumerConsts.OWNER_PARAM,
+            required = false, //
+            value = ConsumerConsts.OWNER_PARAM_DESCRIPTION) //
+        String owner) {
+        try {
+            this.eiTypes.getType(eiTypeId); // Just to check that the type exists
+            List<String> result = new ArrayList<>();
+            for (EiJob job : this.eiJobs.getJobsForType(eiTypeId)) {
+                result.add(job.id());
+            }
+            return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @GetMapping(
+        path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}",
+        produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Individual EI Job", notes = "")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "EI Job", response = ConsumerEiJobInfo.class), //
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type or job is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> getIndividualEiJob( //
+        @PathVariable("eiTypeId") String eiTypeId, //
+        @PathVariable("eiJobId") String eiJobId) {
+        try {
+            this.eiTypes.getType(eiTypeId); // Just to check that the type exists
+            EiJob job = this.eiJobs.getJob(eiJobId);
+            return new ResponseEntity<>(gson.toJson(toEiJobInfo(job)), HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @GetMapping(
+        path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}/status",
+        produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "EI Job status", notes = "")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "EI Job status", response = ConsumerEiJobStatus.class), //
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type or job is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> getEiJobStatus( //
+        @PathVariable("eiTypeId") String eiTypeId, //
+        @PathVariable("eiJobId") String eiJobId) {
+        try {
+            this.eiTypes.getType(eiTypeId); // Just to check that the type exists
+            EiJob job = this.eiJobs.getJob(eiJobId);
+            return new ResponseEntity<>(gson.toJson(toEiJobStatus(job)), HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    private ConsumerEiJobStatus toEiJobStatus(EiJob job) {
+        return new ConsumerEiJobStatus(ConsumerEiJobStatus.OperationalState.ENABLED);
+    }
+
+    @DeleteMapping(
+        path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}",
+        produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Individual EI Job", notes = "Delete EI job")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "Not used", response = void.class),
+            @ApiResponse(code = 204, message = "Job deleted", response = void.class),
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type or job is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> deleteIndividualEiJob( //
+        @PathVariable("eiTypeId") String eiTypeId, //
+        @PathVariable("eiJobId") String eiJobId) {
+        try {
+            this.eiJobs.remove(eiJobId);
+            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    @PutMapping(
+        path = ConsumerConsts.A1E_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")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 201, message = "Job created", response = void.class), //
+            @ApiResponse(code = 200, message = "Job updated", response = void.class), // ,
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information type is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> putIndividualEiJob( //
+        @PathVariable("eiTypeId") String eiTypeId, //
+        @PathVariable("eiJobId") String eiJobId, //
+        @RequestBody ConsumerEiJobInfo eiJobInfo) {
+        try {
+            this.eiTypes.getType(eiTypeId); // Just to check that the type exists
+            final boolean newJob = this.eiJobs.get(eiJobId) == null;
+            this.eiJobs.put(toEiJob(eiJobInfo, eiJobId, eiTypeId));
+            return new ResponseEntity<>(newJob ? HttpStatus.CREATED : HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    // Status TBD
+
+    private EiJob toEiJob(ConsumerEiJobInfo info, String id, String typeId) {
+        return ImmutableEiJob.builder() //
+            .id(id) //
+            .typeId(typeId) //
+            .owner(info.owner) //
+            .jobData(info.jobData) //
+            .build();
+    }
+
+    private ConsumerEiTypeInfo toEiTypeInfo(EiType t) {
+        return new ConsumerEiTypeInfo(t.jobDataSchema());
+    }
+
+    private ConsumerEiJobInfo toEiJobInfo(EiJob s) {
+        return new ConsumerEiJobInfo(s.jobData(), s.owner());
+    }
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java
new file mode 100644 (file)
index 0000000..08d3cae
--- /dev/null
@@ -0,0 +1,52 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.consumer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "ei_job_info", description = "Information for a Enrichment Information Job")
+public class ConsumerEiJobInfo {
+
+    @ApiModelProperty(value = "Identity of the owner of the job")
+    @SerializedName("owner")
+    @JsonProperty("owner")
+    public String owner;
+
+    @ApiModelProperty(value = "EI Type specific job data")
+    @SerializedName("job_data")
+    @JsonProperty("job_data")
+    public Object jobData;
+
+    public ConsumerEiJobInfo() {
+    }
+
+    public ConsumerEiJobInfo(Object jobData, String owner) {
+        this.jobData = jobData;
+        this.owner = owner;
+    }
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java
new file mode 100644 (file)
index 0000000..dbdd1a3
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.consumer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "ei_job_status", description = "Status for an EI Job")
+public class ConsumerEiJobStatus {
+
+    @Gson.TypeAdapters
+    @ApiModel(value = "operational_state", description = "Represents the operational states for a EI Job")
+    public enum OperationalState {
+        ENABLED, DISABLED
+    }
+
+    private static final String OPERATIONAL_STATE_DESCRIPTION = "Operational state, values:\n" //
+        + "ENABLED: TBD\n" //
+        + "DISABLED: TBD.";
+
+    @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "operational_state")
+    @SerializedName("operational_state")
+    @JsonProperty("operational_state")
+    public final OperationalState state;
+
+    public ConsumerEiJobStatus(OperationalState state) {
+        this.state = state;
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiTypeInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiTypeInfo.java
new file mode 100644 (file)
index 0000000..05d2326
--- /dev/null
@@ -0,0 +1,43 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.consumer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "ei_type_info", description = "Information for an EI type")
+public class ConsumerEiTypeInfo {
+
+    @ApiModelProperty(value = "Json schema for the job data")
+    @SerializedName("job_data_schema")
+    @JsonProperty("job_data_schema")
+    public final Object jobDataSchema;
+
+    ConsumerEiTypeInfo(Object jobDataSchema) {
+        this.jobDataSchema = jobDataSchema;
+    }
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/exceptions/ServiceException.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/exceptions/ServiceException.java
new file mode 100644 (file)
index 0000000..a14e8de
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2019 Nordix Foundation. 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.
+ * ============LICENSE_END========================================================================
+ */
+
+package org.oransc.enrichment.exceptions;
+
+public class ServiceException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    public ServiceException(String message) {
+        super(message);
+    }
+
+    public ServiceException(String message, Exception originalException) {
+        super(message, originalException);
+    }
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java
new file mode 100644 (file)
index 0000000..79f62f8
--- /dev/null
@@ -0,0 +1,40 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.repository;
+
+import org.immutables.gson.Gson;
+import org.immutables.value.Value;
+
+/**
+ * Represents the dynamic information about a Near-RT RIC.
+ */
+@Value.Immutable
+@Gson.TypeAdapters
+public interface EiJob {
+
+    String id();
+
+    String typeId();
+
+    String owner();
+
+    Object jobData();
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java
new file mode 100644 (file)
index 0000000..bb2e40f
--- /dev/null
@@ -0,0 +1,106 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.repository;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import org.oransc.enrichment.exceptions.ServiceException;
+
+/**
+ * Dynamic representation of all EI Jobs in the system.
+ */
+public class EiJobs {
+    private Map<String, EiJob> allEiJobs = new HashMap<>();
+    private Map<String, Map<String, EiJob>> jobsByType = new HashMap<>();
+
+    public synchronized void put(EiJob job) {
+        allEiJobs.put(job.id(), job);
+        multiMapPut(this.jobsByType, job.typeId(), job);
+    }
+
+    public synchronized Collection<EiJob> getJobs() {
+        return new Vector<>(allEiJobs.values());
+    }
+
+    public synchronized EiJob getJob(String id) throws ServiceException {
+        EiJob ric = allEiJobs.get(id);
+        if (ric == null) {
+            throw new ServiceException("Could not find EI Job: " + id);
+        }
+        return ric;
+    }
+
+    public synchronized Collection<EiJob> getJobsForType(String typeId) {
+        return multiMapGet(this.jobsByType, typeId);
+    }
+
+    public synchronized EiJob get(String id) {
+        return allEiJobs.get(id);
+    }
+
+    public synchronized EiJob remove(String id) {
+        EiJob job = allEiJobs.get(id);
+        if (job != null) {
+            remove(job);
+        }
+        return job;
+    }
+
+    public synchronized void remove(EiJob job) {
+        this.allEiJobs.remove(job.id());
+        multiMapRemove(this.jobsByType, job.typeId(), job);
+    }
+
+    public synchronized int size() {
+        return allEiJobs.size();
+    }
+
+    public synchronized void clear() {
+        this.allEiJobs.clear();
+    }
+
+    private void multiMapPut(Map<String, Map<String, EiJob>> multiMap, String key, EiJob value) {
+        multiMap.computeIfAbsent(key, k -> new HashMap<>()).put(value.id(), value);
+    }
+
+    private void multiMapRemove(Map<String, Map<String, EiJob>> multiMap, String key, EiJob value) {
+        Map<String, EiJob> map = multiMap.get(key);
+        if (map != null) {
+            map.remove(value.id());
+            if (map.isEmpty()) {
+                multiMap.remove(key);
+            }
+        }
+    }
+
+    private Collection<EiJob> multiMapGet(Map<String, Map<String, EiJob>> multiMap, String key) {
+        Map<String, EiJob> map = multiMap.get(key);
+        if (map == null) {
+            return Collections.emptyList();
+        }
+        return new Vector<>(map.values());
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java
new file mode 100644 (file)
index 0000000..997484d
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.repository;
+
+import org.immutables.gson.Gson;
+import org.immutables.value.Value;
+
+@Value.Immutable
+@Gson.TypeAdapters
+public interface EiType {
+    public String id();
+
+    public Object jobDataSchema();
+}
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java
new file mode 100644 (file)
index 0000000..7668ff1
--- /dev/null
@@ -0,0 +1,68 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.repository;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import org.oransc.enrichment.exceptions.ServiceException;
+
+/**
+ * Dynamic representation of all EI Types in the system.
+ */
+public class EiTypes {
+    Map<String, EiType> allEiTypes = new HashMap<>();
+
+    public synchronized void put(EiType type) {
+        allEiTypes.put(type.id(), type);
+    }
+
+    public synchronized Collection<EiType> getAllEiTypes() {
+        return new Vector<>(allEiTypes.values());
+    }
+
+    public synchronized EiType getType(String id) throws ServiceException {
+        EiType type = allEiTypes.get(id);
+        if (type == null) {
+            throw new ServiceException("Could not find EI Job: " + id);
+        }
+        return type;
+    }
+
+    public synchronized EiType get(String id) {
+        return allEiTypes.get(id);
+    }
+
+    public synchronized void remove(String id) {
+        allEiTypes.remove(id);
+    }
+
+    public synchronized int size() {
+        return allEiTypes.size();
+    }
+
+    public synchronized void clear() {
+        this.allEiTypes.clear();
+    }
+
+}
diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java
new file mode 100644 (file)
index 0000000..d871427
--- /dev/null
@@ -0,0 +1,275 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2019-2020 Nordix Foundation. 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+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 org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.oransc.enrichment.clients.AsyncRestClient;
+import org.oransc.enrichment.configuration.ApplicationConfig;
+import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
+import org.oransc.enrichment.configuration.WebClientConfig;
+import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
+import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
+import org.oransc.enrichment.repository.EiJob;
+import org.oransc.enrichment.repository.EiJobs;
+import org.oransc.enrichment.repository.EiType;
+import org.oransc.enrichment.repository.EiTypes;
+import org.oransc.enrichment.repository.ImmutableEiJob;
+import org.oransc.enrichment.repository.ImmutableEiType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@TestPropertySource(
+    properties = { //
+        "server.ssl.key-store=./config/keystore.jks", //
+        "app.webclient.trust-store=./config/truststore.jks"})
+class ApplicationTest {
+    private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class);
+
+    @Autowired
+    ApplicationContext context;
+
+    @Autowired
+    EiJobs eiJobs;
+
+    @Autowired
+    EiTypes eiTypes;
+
+    @Autowired
+    ApplicationConfig applicationConfig;
+
+    private static Gson gson = new GsonBuilder() //
+        .serializeNulls() //
+        .create(); //
+
+    /**
+     * Overrides the BeanFactory.
+     */
+    @TestConfiguration
+    static class TestBeanFactory {
+
+    }
+
+    @LocalServerPort
+    private int port;
+
+    @BeforeEach
+    void reset() {
+        this.eiJobs.clear();
+        this.eiTypes.clear();
+    }
+
+    @Test
+    void getEiTypes() throws Exception {
+        addEiType("test");
+        String url = "/eitypes";
+        String rsp = restClient().get(url).block();
+        assertThat(rsp).isEqualTo("[\"test\"]");
+    }
+
+    @Test
+    void getEiType() throws Exception {
+        addEiType("test");
+        String url = "/eitypes/test";
+        String rsp = restClient().get(url).block();
+        assertThat(rsp).contains("job_data_schema");
+    }
+
+    @Test
+    void getEiTypeNotFound() throws Exception {
+        String url = "/eitypes/junk";
+        testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI Job: junk");
+    }
+
+    @Test
+    void getEiJobsIds() throws Exception {
+        addEiJob("typeId", "jobId");
+        String url = "/eitypes/typeId/eijobs";
+        String rsp = restClient().get(url).block();
+        assertThat(rsp).isEqualTo("[\"jobId\"]");
+    }
+
+    @Test
+    void getEiJobTypeNotFound() throws Exception {
+        String url = "/eitypes/junk/eijobs";
+        testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI Job: junk");
+    }
+
+    @Test
+    void getEiJob() throws Exception {
+        addEiJob("typeId", "jobId");
+        String url = "/eitypes/typeId/eijobs/jobId";
+        String rsp = restClient().get(url).block();
+        assertThat(rsp).contains("job_data");
+    }
+
+    @Test
+    void getEiJobStatus() throws Exception {
+        addEiJob("typeId", "jobId");
+        String url = "/eitypes/typeId/eijobs/jobId/status";
+        String rsp = restClient().get(url).block();
+        assertThat(rsp).contains("ENABLED");
+    }
+
+    // Status TBD
+
+    @Test
+    void deleteEiJob() throws Exception {
+        addEiJob("typeId", "jobId");
+        assertThat(this.eiJobs.size()).isEqualTo(1);
+        String url = "/eitypes/typeId/eijobs/jobId";
+        restClient().delete(url).block();
+        assertThat(this.eiJobs.size()).isEqualTo(0);
+    }
+
+    @Test
+    void putEiJob() throws Exception {
+        addEiType("typeId");
+
+        String url = "/eitypes/typeId/eijobs/jobId";
+        String body = gson.toJson(eiJobInfo());
+        ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
+        assertThat(this.eiJobs.size()).isEqualTo(1);
+        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+
+        resp = restClient().putForEntity(url, body).block();
+        assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
+        EiJob job = this.eiJobs.getJob("jobId");
+        assertThat(job.owner()).isEqualTo("owner");
+    }
+
+    ConsumerEiJobInfo eiJobInfo() {
+        return new ConsumerEiJobInfo(jsonObject(), "owner");
+    }
+
+    // @Test
+    @SuppressWarnings("squid:S2699")
+    void runMock() throws Exception {
+        logger.info("Keeping server alive! " + this.port);
+        synchronized (this) {
+            this.wait();
+        }
+    }
+
+    JsonObject jsonObject() {
+        JsonObject jsonObj = new JsonObject();
+        JsonElement e = new JsonPrimitive(111);
+        jsonObj.add("param", e);
+        return jsonObj;
+    }
+
+    private EiJob addEiJob(String typeId, String jobId) {
+        addEiType(typeId);
+        EiJob job = ImmutableEiJob.builder() //
+            .id(jobId) //
+            .typeId(typeId) //
+            .owner("owner") //
+            .jobData(jsonObject()) //
+            .build();
+        this.eiJobs.put(job);
+        return job;
+    }
+
+    private EiType addEiType(String typeId) {
+        EiType t = ImmutableEiType.builder() //
+            .id(typeId) //
+            .jobDataSchema(jsonObject()) //
+            .build();
+        this.eiTypes.put(t);
+        return t;
+    }
+
+    private String baseUrl() {
+        return "https://localhost:" + this.port + ConsumerConsts.A1E_API_ROOT;
+    }
+
+    private AsyncRestClient restClient(boolean useTrustValidation) {
+        WebClientConfig config = this.applicationConfig.getWebClientConfig();
+        config = ImmutableWebClientConfig.builder() //
+            .keyStoreType(config.keyStoreType()) //
+            .keyStorePassword(config.keyStorePassword()) //
+            .keyStore(config.keyStore()) //
+            .keyPassword(config.keyPassword()) //
+            .isTrustStoreUsed(useTrustValidation) //
+            .trustStore(config.trustStore()) //
+            .trustStorePassword(config.trustStorePassword()) //
+            .build();
+
+        return new AsyncRestClient(baseUrl(), config);
+    }
+
+    private AsyncRestClient restClient() {
+        return restClient(false);
+    }
+
+    private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
+        testErrorCode(request, expStatus, responseContains, true);
+    }
+
+    private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
+        boolean expectApplicationProblemJsonMediaType) {
+        StepVerifier.create(request) //
+            .expectSubscription() //
+            .expectErrorMatches(
+                t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
+            .verify();
+    }
+
+    private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
+        boolean expectApplicationProblemJsonMediaType) {
+        assertTrue(throwable instanceof WebClientResponseException);
+        WebClientResponseException responseException = (WebClientResponseException) throwable;
+        assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
+        assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
+        if (expectApplicationProblemJsonMediaType) {
+            assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
+        }
+        return true;
+    }
+
+}