X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=enrichment-coordinator-service%2Fsrc%2Fmain%2Fjava%2Forg%2Foransc%2Fenrichment%2Fcontrollers%2Fconsumer%2FConsumerController.java;h=ec83f65cc6911e00de783d1157a7f4d51f7c362f;hb=c5ea745a563f8bae0c0c3de3649a943e167f01cc;hp=7dfaecae9c9ca256633ec10dc0a790bad1b57c1c;hpb=d1d085456c485599f6b8aba87b6d761b29c2ecd4;p=nonrtric.git 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 index 7dfaecae..ec83f65c 100644 --- 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 @@ -1,9 +1,9 @@ /*- * ========================LICENSE_START================================= - * ONAP : ccsdk oran - * ====================================================================== - * Copyright (C) 2019-2020 Nordix Foundation. All rights reserved. - * ====================================================================== + * 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 @@ -20,24 +20,36 @@ package org.oransc.enrichment.controllers.consumer; +import com.fasterxml.jackson.databind.ObjectMapper; 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 io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; +import org.json.JSONObject; +import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.controllers.ErrorResponse; +import org.oransc.enrichment.controllers.VoidResponse; +import org.oransc.enrichment.controllers.producer.ProducerCallbacks; +import org.oransc.enrichment.exceptions.ServiceException; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducers; import org.oransc.enrichment.repository.EiType; import org.oransc.enrichment.repository.EiTypes; -import org.oransc.enrichment.repository.ImmutableEiJob; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -47,112 +59,140 @@ 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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; -@RestController("ConsumerController") -@Api(tags = {ConsumerConsts.CONSUMER_API_NAME}) +@SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. +@RestController("A1-EI") +@Tag(name = ConsumerConsts.CONSUMER_API_NAME) +@RequestMapping(path = ConsumerConsts.API_ROOT, produces = MediaType.APPLICATION_JSON_VALUE) public class ConsumerController { + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Autowired + ApplicationConfig applicationConfig; + @Autowired private EiJobs eiJobs; @Autowired private EiTypes eiTypes; - private static Gson gson = new GsonBuilder() // - .serializeNulls() // - .create(); // + @Autowired + private EiProducers eiProducers; + + @Autowired + ProducerCallbacks producerCallbacks; + + private static Gson gson = new GsonBuilder().create(); - @GetMapping(path = ConsumerConsts.A1E_API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD") + @GetMapping(path = "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "EI type identifiers", description = "") @ApiResponses( value = { // @ApiResponse( - code = 200, - message = "EI type identifiers", - response = String.class, - responseContainer = "List"), // + responseCode = "200", + description = "EI type identifiers", // + content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))), // }) public ResponseEntity getEiTypeIdentifiers( // ) { List result = new ArrayList<>(); for (EiType eiType : this.eiTypes.getAllEiTypes()) { - result.add(eiType.id()); + result.add(eiType.getId()); } 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") + @GetMapping(path = "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Individual EI type", description = "") @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)}) + responseCode = "200", + description = "EI type", // + content = @Content(schema = @Schema(implementation = ConsumerEiTypeInfo.class))), // + @ApiResponse( + responseCode = "404", + description = "Enrichment Information type is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) public ResponseEntity getEiType( // @PathVariable("eiTypeId") String eiTypeId) { try { - EiType t = this.eiTypes.getType(eiTypeId); - ConsumerEiTypeInfo info = toEiTypeInfo(t); + this.eiTypes.getType(eiTypeId); // Make sure that the type exists + ConsumerEiTypeInfo info = toEiTypeInfo(); 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") + @GetMapping(path = "/eijobs", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "EI job identifiers", description = "query for EI job identifiers") @ApiResponses( value = { // @ApiResponse( - code = 200, - message = "EI job identifiers", - response = String.class, - responseContainer = "List"), // + responseCode = "200", + description = "EI job identifiers", // + content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))), @ApiResponse( - code = 404, - message = "Enrichment Information type is not found", - response = ErrorResponse.ErrorInfo.class)}) + responseCode = "404", + description = "Enrichment Information type is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) public ResponseEntity getEiJobIds( // - @PathVariable("eiTypeId") String eiTypeId, // - @ApiParam( + @Parameter( + name = ConsumerConsts.EI_TYPE_ID_PARAM, + required = false, // + description = ConsumerConsts.EI_TYPE_ID_PARAM_DESCRIPTION) // + @RequestParam(name = ConsumerConsts.EI_TYPE_ID_PARAM, required = false) String eiTypeId, + @Parameter( name = ConsumerConsts.OWNER_PARAM, required = false, // - value = ConsumerConsts.OWNER_PARAM_DESCRIPTION) // - String owner) { + description = ConsumerConsts.OWNER_PARAM_DESCRIPTION) // + @RequestParam(name = ConsumerConsts.OWNER_PARAM, required = false) String owner) { try { - this.eiTypes.getType(eiTypeId); // Just to check that the type exists List result = new ArrayList<>(); - for (EiJob job : this.eiJobs.getJobsForType(eiTypeId)) { - result.add(job.id()); + if (owner != null) { + for (EiJob job : this.eiJobs.getJobsForOwner(owner)) { + if (eiTypeId == null || job.getTypeId().equals(eiTypeId)) { + result.add(job.getId()); + } + } + } else if (eiTypeId != null) { + this.eiJobs.getJobsForType(eiTypeId).forEach(job -> result.add(job.getId())); + } else { + this.eiJobs.getJobs().forEach(job -> result.add(job.getId())); } return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK); - } catch (Exception e) { + } 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 = "") + @GetMapping(path = "/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) // + @Operation(summary = "Individual EI job", description = "") // @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)}) + responseCode = "200", + description = "EI job", // + content = @Content(schema = @Schema(implementation = ConsumerEiJobInfo.class))), // + @ApiResponse( + responseCode = "404", + description = "Enrichment Information job is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) public ResponseEntity 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) { @@ -160,22 +200,22 @@ public class ConsumerController { } } - @GetMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}/status", - produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "EI Job status", notes = "") + @GetMapping(path = "/eijobs/{eiJobId}/status", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "EI job status", description = "") @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)}) + responseCode = "200", + description = "EI job status", // + content = @Content(schema = @Schema(implementation = ConsumerEiJobStatus.class))), // + @ApiResponse( + responseCode = "404", + description = "Enrichment Information job is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) public ResponseEntity 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) { @@ -184,26 +224,34 @@ public class ConsumerController { } private ConsumerEiJobStatus toEiJobStatus(EiJob job) { - return new ConsumerEiJobStatus(ConsumerEiJobStatus.OperationalState.ENABLED); + return this.eiProducers.isJobEnabled(job) + ? new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED) + : new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); + } - @DeleteMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", - produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "Individual EI Job", notes = "Delete EI job") + @DeleteMapping(path = "/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Individual EI job", description = "") @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)}) + responseCode = "200", + description = "Not used", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), + @ApiResponse( + responseCode = "204", + description = "Job deleted", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), // + @ApiResponse( + responseCode = "404", + description = "Enrichment Information job is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) public ResponseEntity deleteIndividualEiJob( // - @PathVariable("eiTypeId") String eiTypeId, // @PathVariable("eiJobId") String eiJobId) { try { - this.eiJobs.remove(eiJobId); + EiJob job = this.eiJobs.getJob(eiJobId); + this.eiJobs.remove(job, this.eiProducers); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); @@ -211,48 +259,95 @@ public class ConsumerController { } @PutMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", // + path = "/eijobs/{eiJobId}", // produces = MediaType.APPLICATION_JSON_VALUE, // consumes = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "Individual EI Job", notes = "Create or update an EI Job") + @Operation(summary = "Individual EI job", description = "") @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 putIndividualEiJob( // - @PathVariable("eiTypeId") String eiTypeId, // + responseCode = "201", + description = "Job created", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), // + @ApiResponse( + responseCode = "200", + description = "Job updated", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), // + @ApiResponse( + responseCode = "404", + description = "Enrichment Information type is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) + public Mono> putIndividualEiJob( // @PathVariable("eiJobId") String eiJobId, // - @RequestBody ConsumerEiJobInfo eiJobInfo) { + @RequestBody ConsumerEiJobInfo eiJobObject) { + + final boolean isNewJob = this.eiJobs.get(eiJobId) == null; + + return validatePutEiJob(eiJobId, eiJobObject) // + .flatMap(this::startEiJob) // + .doOnNext(newEiJob -> this.eiJobs.put(newEiJob)) // + .flatMap(newEiJob -> Mono.just(new ResponseEntity<>(isNewJob ? HttpStatus.CREATED : HttpStatus.OK))) + .onErrorResume(throwable -> Mono.just(ErrorResponse.create(throwable, HttpStatus.NOT_FOUND))); + } + + private Mono startEiJob(EiJob newEiJob) { + return this.producerCallbacks.startEiJob(newEiJob, eiProducers) // + .doOnNext(noOfAcceptingProducers -> this.logger.debug( + "Started EI job {}, number of activated producers: {}", newEiJob.getId(), noOfAcceptingProducers)) // + .flatMap(noOfAcceptingProducers -> Mono.just(newEiJob)); + } + + private Mono validatePutEiJob(String eiJobId, 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); + EiType eiType = this.eiTypes.getType(eiJobInfo.eiTypeId); + validateJsonObjectAgainstSchema(eiType.getJobDataSchema(), eiJobInfo.jobDefinition); + EiJob existingEiJob = this.eiJobs.get(eiJobId); + + if (existingEiJob != null && !existingEiJob.getTypeId().equals(eiJobInfo.eiTypeId)) { + throw new ServiceException("Not allowed to change type for existing EI job", HttpStatus.CONFLICT); + } + return Mono.just(toEiJob(eiJobInfo, eiJobId, eiType)); } catch (Exception e) { - return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + return Mono.error(e); } } - // Status TBD + private void validateJsonObjectAgainstSchema(Object schemaObj, Object object) throws ServiceException { + if (schemaObj != null) { // schema is optional for now + try { + ObjectMapper mapper = new ObjectMapper(); + + String schemaAsString = mapper.writeValueAsString(schemaObj); + JSONObject schemaJSON = new JSONObject(schemaAsString); + org.everit.json.schema.Schema schema = org.everit.json.schema.loader.SchemaLoader.load(schemaJSON); + + String objectAsString = mapper.writeValueAsString(object); + JSONObject json = new JSONObject(objectAsString); + schema.validate(json); + } catch (Exception e) { + throw new ServiceException("Json validation failure " + e.toString(), HttpStatus.CONFLICT); + } + } + } - private EiJob toEiJob(ConsumerEiJobInfo info, String id, String typeId) { - return ImmutableEiJob.builder() // + private EiJob toEiJob(ConsumerEiJobInfo info, String id, EiType type) { + return EiJob.builder() // .id(id) // - .typeId(typeId) // + .typeId(type.getId()) // .owner(info.owner) // - .jobData(info.jobData) // + .jobData(info.jobDefinition) // + .targetUrl(info.jobResultUri) // + .jobStatusUrl(info.statusNotificationUri == null ? "" : info.statusNotificationUri) // .build(); } - private ConsumerEiTypeInfo toEiTypeInfo(EiType t) { - return new ConsumerEiTypeInfo(t.jobDataSchema()); + private ConsumerEiTypeInfo toEiTypeInfo() { + return new ConsumerEiTypeInfo(); } private ConsumerEiJobInfo toEiJobInfo(EiJob s) { - return new ConsumerEiJobInfo(s.jobData(), s.owner()); + return new ConsumerEiJobInfo(s.getTypeId(), s.getJobData(), s.getOwner(), s.getTargetUrl(), + s.getJobStatusUrl()); } }