Clean up of enrichement pom
[nonrtric.git] / enrichment-coordinator-service / src / main / java / org / oransc / enrichment / controllers / consumer / ConsumerController.java
1 /*-
2  * ========================LICENSE_START=================================
3  * ONAP : ccsdk oran
4  * ======================================================================
5  * Copyright (C) 2019-2020 Nordix Foundation. All rights reserved.
6  * ======================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 package org.oransc.enrichment.controllers.consumer;
22
23 import com.fasterxml.jackson.databind.ObjectMapper;
24 import com.google.gson.Gson;
25 import com.google.gson.GsonBuilder;
26
27 import io.swagger.annotations.Api;
28 import io.swagger.annotations.ApiOperation;
29 import io.swagger.annotations.ApiParam;
30 import io.swagger.annotations.ApiResponse;
31 import io.swagger.annotations.ApiResponses;
32
33 import java.lang.invoke.MethodHandles;
34 import java.util.ArrayList;
35 import java.util.List;
36
37 import org.everit.json.schema.Schema;
38 import org.everit.json.schema.loader.SchemaLoader;
39 import org.json.JSONObject;
40 import org.oransc.enrichment.clients.ProducerCallbacks;
41 import org.oransc.enrichment.configuration.ApplicationConfig;
42 import org.oransc.enrichment.controllers.ErrorResponse;
43 import org.oransc.enrichment.exceptions.ServiceException;
44 import org.oransc.enrichment.repository.EiJob;
45 import org.oransc.enrichment.repository.EiJobs;
46 import org.oransc.enrichment.repository.EiType;
47 import org.oransc.enrichment.repository.EiTypes;
48 import org.oransc.enrichment.repository.ImmutableEiJob;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.beans.factory.annotation.Autowired;
52 import org.springframework.http.HttpStatus;
53 import org.springframework.http.MediaType;
54 import org.springframework.http.ResponseEntity;
55 import org.springframework.web.bind.annotation.DeleteMapping;
56 import org.springframework.web.bind.annotation.GetMapping;
57 import org.springframework.web.bind.annotation.PathVariable;
58 import org.springframework.web.bind.annotation.PutMapping;
59 import org.springframework.web.bind.annotation.RequestBody;
60 import org.springframework.web.bind.annotation.RestController;
61 import reactor.core.publisher.Mono;
62
63 @SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string ..
64 @RestController("ConsumerController")
65 @Api(tags = {ConsumerConsts.CONSUMER_API_NAME})
66 public class ConsumerController {
67
68     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
69
70     @Autowired
71     ApplicationConfig applicationConfig;
72
73     @Autowired
74     private EiJobs eiJobs;
75
76     @Autowired
77     private EiTypes eiTypes;
78
79     @Autowired
80     ProducerCallbacks producerCallbacks;
81
82     private static Gson gson = new GsonBuilder() //
83         .serializeNulls() //
84         .create(); //
85
86     @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE)
87     @ApiOperation(value = "EI type identifiers", notes = "")
88     @ApiResponses(
89         value = { //
90             @ApiResponse(
91                 code = 200,
92                 message = "EI type identifiers",
93                 response = String.class,
94                 responseContainer = "List"), //
95         })
96     public ResponseEntity<Object> getEiTypeIdentifiers( //
97     ) {
98         List<String> result = new ArrayList<>();
99         for (EiType eiType : this.eiTypes.getAllEiTypes()) {
100             result.add(eiType.getId());
101         }
102
103         return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
104     }
105
106     @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE)
107     @ApiOperation(value = "Individual EI type", notes = "")
108     @ApiResponses(
109         value = { //
110             @ApiResponse(code = 200, message = "EI type", response = ConsumerEiTypeInfo.class), //
111             @ApiResponse(
112                 code = 404,
113                 message = "Enrichment Information type is not found",
114                 response = ErrorResponse.ErrorInfo.class)})
115     public ResponseEntity<Object> getEiType( //
116         @PathVariable("eiTypeId") String eiTypeId) {
117         try {
118             EiType t = this.eiTypes.getType(eiTypeId);
119             ConsumerEiTypeInfo info = toEiTypeInfo(t);
120             return new ResponseEntity<>(gson.toJson(info), HttpStatus.OK);
121         } catch (Exception e) {
122             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
123         }
124     }
125
126     @GetMapping(
127         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs",
128         produces = MediaType.APPLICATION_JSON_VALUE)
129     @ApiOperation(value = "EI job identifiers", notes = "")
130     @ApiResponses(
131         value = { //
132             @ApiResponse(
133                 code = 200,
134                 message = "EI job identifiers",
135                 response = String.class,
136                 responseContainer = "List"), //
137             @ApiResponse(
138                 code = 404,
139                 message = "Enrichment Information type is not found",
140                 response = ErrorResponse.ErrorInfo.class)})
141     public ResponseEntity<Object> getEiJobIds( //
142         @PathVariable("eiTypeId") String eiTypeId, //
143         @ApiParam(
144             name = ConsumerConsts.OWNER_PARAM,
145             required = false, //
146             value = ConsumerConsts.OWNER_PARAM_DESCRIPTION) //
147         String owner) {
148         try {
149             this.eiTypes.getType(eiTypeId); // Just to check that the type exists
150             List<String> result = new ArrayList<>();
151             for (EiJob job : this.eiJobs.getJobsForType(eiTypeId)) {
152                 result.add(job.id());
153             }
154             return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
155         } catch (Exception e) {
156             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
157         }
158     }
159
160     @GetMapping(
161         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}",
162         produces = MediaType.APPLICATION_JSON_VALUE)
163     @ApiOperation(value = "Individual EI Job", notes = "")
164     @ApiResponses(
165         value = { //
166             @ApiResponse(code = 200, message = "EI Job", response = ConsumerEiJobInfo.class), //
167             @ApiResponse(
168                 code = 404,
169                 message = "Enrichment Information type or job is not found",
170                 response = ErrorResponse.ErrorInfo.class)})
171     public ResponseEntity<Object> getIndividualEiJob( //
172         @PathVariable("eiTypeId") String eiTypeId, //
173         @PathVariable("eiJobId") String eiJobId) {
174         try {
175             this.eiTypes.getType(eiTypeId); // Just to check that the type exists
176             EiJob job = this.eiJobs.getJob(eiJobId);
177             return new ResponseEntity<>(gson.toJson(toEiJobInfo(job)), HttpStatus.OK);
178         } catch (Exception e) {
179             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
180         }
181     }
182
183     @GetMapping(
184         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}/status",
185         produces = MediaType.APPLICATION_JSON_VALUE)
186     @ApiOperation(value = "EI Job status", notes = "")
187     @ApiResponses(
188         value = { //
189             @ApiResponse(code = 200, message = "EI Job status", response = ConsumerEiJobStatus.class), //
190             @ApiResponse(
191                 code = 404,
192                 message = "Enrichment Information type or job is not found",
193                 response = ErrorResponse.ErrorInfo.class)})
194     public ResponseEntity<Object> getEiJobStatus( //
195         @PathVariable("eiTypeId") String eiTypeId, //
196         @PathVariable("eiJobId") String eiJobId) {
197         try {
198             this.eiTypes.getType(eiTypeId); // Just to check that the type exists
199             EiJob job = this.eiJobs.getJob(eiJobId);
200             return new ResponseEntity<>(gson.toJson(toEiJobStatus(job)), HttpStatus.OK);
201         } catch (Exception e) {
202             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
203         }
204     }
205
206     private ConsumerEiJobStatus toEiJobStatus(EiJob job) {
207         // TODO
208         return new ConsumerEiJobStatus(ConsumerEiJobStatus.OperationalState.ENABLED);
209     }
210
211     @DeleteMapping(
212         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}",
213         produces = MediaType.APPLICATION_JSON_VALUE)
214     @ApiOperation(value = "Individual EI Job", notes = "")
215     @ApiResponses(
216         value = { //
217             @ApiResponse(code = 200, message = "Not used", response = void.class),
218             @ApiResponse(code = 204, message = "Job deleted", response = void.class),
219             @ApiResponse(
220                 code = 404,
221                 message = "Enrichment Information type or job is not found",
222                 response = ErrorResponse.ErrorInfo.class)})
223     public ResponseEntity<Object> deleteIndividualEiJob( //
224         @PathVariable("eiTypeId") String eiTypeId, //
225         @PathVariable("eiJobId") String eiJobId) {
226         try {
227             EiJob job = this.eiJobs.getJob(eiJobId);
228             this.eiJobs.remove(job);
229             this.producerCallbacks.notifyProducersJobDeleted(job);
230             return new ResponseEntity<>(HttpStatus.NO_CONTENT);
231         } catch (Exception e) {
232             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
233         }
234     }
235
236     @PutMapping(
237         path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", //
238         produces = MediaType.APPLICATION_JSON_VALUE, //
239         consumes = MediaType.APPLICATION_JSON_VALUE)
240     @ApiOperation(value = "Individual EI Job", notes = "")
241     @ApiResponses(
242         value = { //
243             @ApiResponse(code = 201, message = "Job created", response = void.class), //
244             @ApiResponse(code = 200, message = "Job updated", response = void.class), // ,
245             @ApiResponse(
246                 code = 404,
247                 message = "Enrichment Information type is not found",
248                 response = ErrorResponse.ErrorInfo.class)})
249     public Mono<ResponseEntity<Object>> putIndividualEiJob( //
250         @PathVariable("eiTypeId") String eiTypeId, //
251         @PathVariable("eiJobId") String eiJobId, //
252         @RequestBody ConsumerEiJobInfo eiJobInfo) {
253
254         final boolean isNewJob = this.eiJobs.get(eiJobId) == null;
255
256         return validatePutEiJob(eiTypeId, eiJobId, eiJobInfo) //
257             .flatMap(this::notifyProducersNewJob) //
258             .doOnNext(newEiJob -> this.eiJobs.put(newEiJob)) //
259             .flatMap(newEiJob -> Mono.just(new ResponseEntity<>(isNewJob ? HttpStatus.CREATED : HttpStatus.OK)))
260             .onErrorResume(throwable -> Mono.just(ErrorResponse.create(throwable, HttpStatus.NOT_FOUND)));
261     }
262
263     private Mono<EiJob> notifyProducersNewJob(EiJob newEiJob) {
264         return this.producerCallbacks.notifyProducersJobStarted(newEiJob) //
265             .flatMap(noOfAcceptingProducers -> {
266                 if (noOfAcceptingProducers.intValue() > 0) {
267                     return Mono.just(newEiJob);
268                 } else {
269                     return Mono.error(new ServiceException("Job not accepted by any producers", HttpStatus.CONFLICT));
270                 }
271             });
272     }
273
274     private Mono<EiJob> validatePutEiJob(String eiTypeId, String eiJobId, ConsumerEiJobInfo eiJobInfo) {
275         try {
276             EiType eiType = this.eiTypes.getType(eiTypeId);
277             validateJsonObjectAgainstSchema(eiType.getJobDataSchema(), eiJobInfo.jobData);
278             EiJob existingEiJob = this.eiJobs.get(eiJobId);
279
280             if (existingEiJob != null && !existingEiJob.type().getId().equals(eiTypeId)) {
281                 throw new ServiceException("Not allowed to change type for existing EI job", HttpStatus.CONFLICT);
282             }
283             return Mono.just(toEiJob(eiJobInfo, eiJobId, eiType));
284         } catch (Exception e) {
285             return Mono.error(e);
286         }
287     }
288
289     private void validateJsonObjectAgainstSchema(Object schemaObj, Object object) throws ServiceException {
290         if (schemaObj != null) { // schema is optional for now
291             try {
292                 ObjectMapper mapper = new ObjectMapper();
293
294                 String schemaAsString = mapper.writeValueAsString(schemaObj);
295                 JSONObject schemaJSON = new JSONObject(schemaAsString);
296                 Schema schema = SchemaLoader.load(schemaJSON);
297
298                 String objectAsString = mapper.writeValueAsString(object);
299                 JSONObject json = new JSONObject(objectAsString);
300                 schema.validate(json);
301             } catch (Exception e) {
302                 throw new ServiceException("Json validation failure " + e.toString(), HttpStatus.CONFLICT);
303             }
304         }
305     }
306
307     // Status TBD
308
309     private EiJob toEiJob(ConsumerEiJobInfo info, String id, EiType type) {
310         return ImmutableEiJob.builder() //
311             .id(id) //
312             .type(type) //
313             .owner(info.owner) //
314             .jobData(info.jobData) //
315             .targetUri(info.targetUri) //
316             .build();
317     }
318
319     private ConsumerEiTypeInfo toEiTypeInfo(EiType t) {
320         return new ConsumerEiTypeInfo(t.getJobDataSchema());
321     }
322
323     private ConsumerEiJobInfo toEiJobInfo(EiJob s) {
324         return new ConsumerEiJobInfo(s.jobData(), s.owner(), s.targetUri());
325     }
326 }