If a consumer creates a job and the requested type does not exist, a compatible type may be automatically chosen.
The type with the lowest version number is chosen.
All producers that has been registerred to support a compatible type are notified.
The producers will one of its registerred types (the one with the lowest number).
Change-Id: I94580f3fd1e07c5b0483232e06d1950c2c573fea
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
Issue-ID: NONRTRIC-810
},
"delete": {
"summary": "Individual Information Type",
+ "description": "Existing jobs of the type will be automatically deleted.",
"operationId": "deleteInfoType",
"responses": {
"200": {
}},
"/A1-EI/v1/eijobs/{eiJobId}/status": {"get": {
"summary": "EI job status",
- "operationId": "getEiJobStatus_1",
+ "operationId": "getEiJobStatus",
"responses": {
"200": {
"description": "EI job status",
}},
"/data-consumer/v1/info-jobs/{infoJobId}/status": {"get": {
"summary": "Job status",
- "operationId": "getEiJobStatus",
+ "operationId": "getInfoJobStatus",
"responses": {
"200": {
"description": "Information subscription job status",
"/A1-EI/v1/eijobs/{eiJobId}": {
"get": {
"summary": "Individual EI job",
- "operationId": "getIndividualEiJob_1",
+ "operationId": "getIndividualEiJob",
"responses": {
"200": {
"description": "EI job",
},
"delete": {
"summary": "Individual EI job",
- "operationId": "deleteIndividualEiJob_1",
+ "operationId": "deleteIndividualEiJob",
"responses": {
"200": {
"description": "Not used",
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EiJobObject"}}},
"required": true
},
+ "description": "If the requested info_type_id is not found, an attempt to find a compatible version is made. As an example, 'type_1.9.0' is backwards compatible with 'type_1.0.0'",
"operationId": "putIndividualEiJob",
"responses": {
"200": {
"/data-consumer/v1/info-jobs/{infoJobId}": {
"get": {
"summary": "Individual data subscription job",
- "operationId": "getIndividualEiJob",
+ "operationId": "getIndividualInfoJob",
"responses": {
"200": {
"description": "Information subscription job",
},
"delete": {
"summary": "Individual data subscription job",
- "operationId": "deleteIndividualEiJob",
+ "operationId": "deleteIndividualInfoJob",
"responses": {
"200": {
"description": "Not used",
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/consumer_job"}}},
"required": true
},
- "description": "The job will be enabled when a producer is available",
+ "description": "The job will be enabled when a producer is available. If the requested info_type_id is not found, an attempt to find a compatible version is made. As an example, 'type_1.9.0' is backwards compatible with 'type_1.0.0'",
"operationId": "putIndividualInfoJob",
"responses": {
"200": {
"schema": {"type": "string"},
"in": "query",
"name": "infoTypeId",
- "description": "If given, only the producers for the EI Data type is returned.",
+ "description": "If given, only the producers for the Info Type is returned.",
"required": false
}],
"tags": ["Data producer (registration)"]
tags:
- Data producer (registration)
summary: Individual Information Type
+ description: Existing jobs of the type will be automatically deleted.
operationId: deleteInfoType
parameters:
- name: infoTypeId
tags:
- A1-EI (registration)
summary: EI job status
- operationId: getEiJobStatus_1
+ operationId: getEiJobStatus
parameters:
- name: eiJobId
in: path
tags:
- Data consumer
summary: Job status
- operationId: getEiJobStatus
+ operationId: getInfoJobStatus
parameters:
- name: infoJobId
in: path
tags:
- A1-EI (registration)
summary: Individual EI job
- operationId: getIndividualEiJob_1
+ operationId: getIndividualEiJob
parameters:
- name: eiJobId
in: path
tags:
- A1-EI (registration)
summary: Individual EI job
+ description: If the requested info_type_id is not found, an attempt to find
+ a compatible version is made. As an example, 'type_1.9.0' is backwards compatible
+ with 'type_1.0.0'
operationId: putIndividualEiJob
parameters:
- name: eiJobId
tags:
- A1-EI (registration)
summary: Individual EI job
- operationId: deleteIndividualEiJob_1
+ operationId: deleteIndividualEiJob
parameters:
- name: eiJobId
in: path
tags:
- Data consumer
summary: Individual data subscription job
- operationId: getIndividualEiJob
+ operationId: getIndividualInfoJob
parameters:
- name: infoJobId
in: path
tags:
- Data consumer
summary: Individual data subscription job
- description: The job will be enabled when a producer is available
+ description: The job will be enabled when a producer is available. If the requested
+ info_type_id is not found, an attempt to find a compatible version is made.
+ As an example, 'type_1.9.0' is backwards compatible with 'type_1.0.0'
operationId: putIndividualInfoJob
parameters:
- name: infoJobId
tags:
- Data consumer
summary: Individual data subscription job
- operationId: deleteIndividualEiJob
+ operationId: deleteIndividualInfoJob
parameters:
- name: infoJobId
in: path
parameters:
- name: infoTypeId
in: query
- description: If given, only the producers for the EI Data type is returned.
+ description: If given, only the producers for the Info Type is returned.
required: false
style: form
explode: true
}
@Bean
- public InfoJobs infoJobs(SecurityContext securityContext) {
+ public InfoJobs infoJobs(SecurityContext securityContext, InfoTypes types) {
if (infoJobs == null) {
- infoJobs = new InfoJobs(getApplicationConfig(), producerCallbacks(securityContext));
+ infoJobs = new InfoJobs(getApplicationConfig(), types, producerCallbacks(securityContext));
try {
infoJobs.restoreJobsFromDatabase();
} catch (Exception e) {
public static final String EI_TYPE_ID_PARAM = "eiTypeId";
public static final String EI_TYPE_ID_PARAM_DESCRIPTION = "selects EI jobs of matching EI type";
+ public static final String PUT_INDIVIDUAL_JOB_DESCRIPTION =
+ "If the requested info_type_id is not found, an attempt to find a compatible version is made. " //
+ + "As an example, 'type_1.9.0' is backwards compatible with 'type_1.0.0'";
+
private A1eConsts() {
}
}
ApplicationConfig applicationConfig;
@Autowired
- private InfoJobs eiJobs;
+ private InfoJobs infoJobs;
@Autowired
- private InfoTypes eiTypes;
+ private InfoTypes infoTypes;
@Autowired
private InfoProducers infoProducers;
public ResponseEntity<Object> getEiTypeIdentifiers( //
) {
List<String> result = new ArrayList<>();
- for (InfoType eiType : this.eiTypes.getAllInfoTypes()) {
+ for (InfoType eiType : this.infoTypes.getAllInfoTypes()) {
result.add(eiType.getId());
}
public ResponseEntity<Object> getEiType( //
@PathVariable("eiTypeId") String eiTypeId) {
try {
- this.eiTypes.getType(eiTypeId); // Make sure that the type exists
+ this.infoTypes.getType(eiTypeId); // Make sure that the type exists
A1eEiTypeInfo info = toEiTypeInfo();
return new ResponseEntity<>(gson.toJson(info), HttpStatus.OK);
} catch (Exception e) {
try {
List<String> result = new ArrayList<>();
if (owner != null) {
- for (InfoJob job : this.eiJobs.getJobsForOwner(owner)) {
- if (eiTypeId == null || job.getTypeId().equals(eiTypeId)) {
+ for (InfoJob job : this.infoJobs.getJobsForOwner(owner)) {
+ if (eiTypeId == null || job.getType().getId().equals(eiTypeId)) {
result.add(job.getId());
}
}
} else if (eiTypeId != null) {
- this.eiJobs.getJobsForType(eiTypeId).forEach(job -> result.add(job.getId()));
+ this.infoJobs.getJobsForType(eiTypeId).forEach(job -> result.add(job.getId()));
} else {
- this.eiJobs.getJobs().forEach(job -> result.add(job.getId()));
+ this.infoJobs.getJobs().forEach(job -> result.add(job.getId()));
}
return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
} catch (
public ResponseEntity<Object> getIndividualEiJob( //
@PathVariable("eiJobId") String eiJobId) {
try {
- InfoJob job = this.eiJobs.getJob(eiJobId);
+ InfoJob job = this.infoJobs.getJob(eiJobId);
return new ResponseEntity<>(gson.toJson(toEiJobInfo(job)), HttpStatus.OK);
} catch (Exception e) {
return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
public ResponseEntity<Object> getEiJobStatus( //
@PathVariable("eiJobId") String eiJobId) {
try {
- InfoJob job = this.eiJobs.getJob(eiJobId);
+ InfoJob job = this.infoJobs.getJob(eiJobId);
return new ResponseEntity<>(gson.toJson(toEiJobStatus(job)), HttpStatus.OK);
} catch (Exception e) {
return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
public ResponseEntity<Object> deleteIndividualEiJob( //
@PathVariable("eiJobId") String eiJobId) {
try {
- InfoJob job = this.eiJobs.getJob(eiJobId);
- this.eiJobs.remove(job, this.infoProducers);
+ InfoJob job = this.infoJobs.getJob(eiJobId);
+ this.infoJobs.remove(job, this.infoProducers);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
path = "/eijobs/{eiJobId}", //
produces = MediaType.APPLICATION_JSON_VALUE, //
consumes = MediaType.APPLICATION_JSON_VALUE)
- @Operation(summary = "Individual EI job", description = "")
+ @Operation(summary = "Individual EI job", description = A1eConsts.PUT_INDIVIDUAL_JOB_DESCRIPTION)
@ApiResponses(
value = { //
@ApiResponse(
public Mono<ResponseEntity<Object>> putIndividualEiJob( //
@PathVariable("eiJobId") String eiJobId, //
- @RequestBody A1eEiJobInfo eiJobObject) {
+ @RequestBody A1eEiJobInfo eiJobObject) throws ServiceException {
- final boolean isNewJob = this.eiJobs.get(eiJobId) == null;
+ final boolean isNewJob = this.infoJobs.get(eiJobId) == null;
+ InfoType eiType = this.infoTypes.getCompatibleType(eiJobObject.eiTypeId);
- return validatePutEiJob(eiJobId, eiJobObject) //
- .flatMap(this::startEiJob) //
- .doOnNext(newEiJob -> this.eiJobs.put(newEiJob)) //
+ return validatePutEiJob(eiJobId, eiType, eiJobObject) //
+ .flatMap(job -> startEiJob(job, eiType)) //
+ .doOnNext(newEiJob -> this.infoJobs.put(newEiJob)) //
.map(newEiJob -> new ResponseEntity<>(isNewJob ? HttpStatus.CREATED : HttpStatus.OK)) //
.onErrorResume(throwable -> Mono.just(ErrorResponse.create(throwable, HttpStatus.INTERNAL_SERVER_ERROR)));
}
- private Mono<InfoJob> startEiJob(InfoJob newEiJob) {
- return this.producerCallbacks.startInfoSubscriptionJob(newEiJob, infoProducers) //
+ private Mono<InfoJob> startEiJob(InfoJob newEiJob, InfoType type) {
+ return this.producerCallbacks.startInfoSubscriptionJob(newEiJob, type, infoProducers) //
.doOnNext(noOfAcceptingProducers -> this.logger.debug(
"Started EI job {}, number of activated producers: {}", newEiJob.getId(), noOfAcceptingProducers)) //
.map(noOfAcceptingProducers -> newEiJob);
}
- private Mono<InfoJob> validatePutEiJob(String eiJobId, A1eEiJobInfo eiJobInfo) {
+ private Mono<InfoJob> validatePutEiJob(String eiJobId, InfoType eiType, A1eEiJobInfo eiJobInfo) {
try {
- InfoType eiType = this.eiTypes.getType(eiJobInfo.eiTypeId);
validateJsonObjectAgainstSchema(eiType.getJobDataSchema(), eiJobInfo.jobDefinition);
- InfoJob existingEiJob = this.eiJobs.get(eiJobId);
validateUri(eiJobInfo.jobResultUri);
validateUri(eiJobInfo.statusNotificationUri);
- if (existingEiJob != null && !existingEiJob.getTypeId().equals(eiJobInfo.eiTypeId)) {
- throw new ServiceException("Not allowed to change type for existing EI job", HttpStatus.CONFLICT);
+ InfoJob existingEiJob = this.infoJobs.get(eiJobId);
+ if (existingEiJob != null) {
+ InfoType.TypeId typeId = InfoType.TypeId.ofString(eiJobInfo.eiTypeId);
+ if (!existingEiJob.getType().getId().contains(typeId.getName())) {
+ throw new ServiceException("Not allowed to change type for existing EI job", HttpStatus.CONFLICT);
+ }
}
return Mono.just(toEiJob(eiJobInfo, eiJobId, eiType));
} catch (Exception e) {
private InfoJob toEiJob(A1eEiJobInfo info, String id, InfoType type) {
return InfoJob.builder() //
.id(id) //
- .typeId(type.getId()) //
+ .type(type) //
.owner(info.owner) //
.jobData(info.jobDefinition) //
.targetUrl(info.jobResultUri) //
}
private A1eEiJobInfo toEiJobInfo(InfoJob s) {
- return new A1eEiJobInfo(s.getTypeId(), s.getJobData(), s.getOwner(), s.getTargetUrl(), s.getJobStatusUrl());
+ return new A1eEiJobInfo(s.getType().getId(), s.getJobData(), s.getOwner(), s.getTargetUrl(),
+ s.getJobStatusUrl());
}
}
public static final String INDIVIDUAL_JOB = "Individual data subscription job";
- public static final String PUT_INDIVIDUAL_JOB_DESCRIPTION = "The job will be enabled when a producer is available";
+ public static final String PUT_INDIVIDUAL_JOB_DESCRIPTION = "The job will be enabled when a producer is available. "
+ + "If the requested info_type_id is not found, an attempt to find a compatible version is made. " //
+ + "As an example, 'type_1.9.0' is backwards compatible with 'type_1.0.0'";
public static final String INFO_TYPE_ID_PARAM = "infoTypeId";
public static final String INFO_TYPE_ID_PARAM_DESCRIPTION =
List<String> result = new ArrayList<>();
if (owner != null) {
for (InfoJob job : this.infoJobs.getJobsForOwner(owner)) {
- if (infoTypeId == null || job.getTypeId().equals(infoTypeId)) {
+ if (infoTypeId == null || job.getType().getId().equals(infoTypeId)) {
result.add(job.getId());
}
}
this.infoJobs.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);
}
}
@RequestParam(name = ConsumerConsts.OWNER_PARAM, required = true) String owner) {
for (InfoJob job : this.infoJobs.getJobsForOwner(owner)) {
-
- logger.debug("DELETE info jobs, id: {}, type: {}, owner: {}", job.getId(), job.getTypeId(), owner);
+ logger.debug("DELETE info jobs, id: {}, type: {}, owner: {}", job.getId(), job.getType().getId(), owner);
this.infoJobs.remove(job, this.infoProducers);
}
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
description = "Information subscription job is not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> getIndividualEiJob( //
+ public ResponseEntity<Object> getIndividualInfoJob( //
@PathVariable(ConsumerConsts.INFO_JOB_ID_PATH) String infoJobId) {
try {
logger.debug("GET info job, id: {}", infoJobId);
description = "Information subscription job is not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> getEiJobStatus( //
+ public ResponseEntity<Object> getInfoJobStatus( //
@PathVariable(ConsumerConsts.INFO_JOB_ID_PATH) String jobId) {
try {
logger.debug("GET info job status, id: {}", jobId);
private ConsumerJobStatus toInfoJobStatus(InfoJob job) {
Collection<String> producerIds = new ArrayList<>();
- this.infoProducers.getProducersForType(job.getTypeId()).forEach(producer -> producerIds.add(producer.getId()));
+ this.infoProducers.getProducersSupportingType(job.getType())
+ .forEach(producer -> producerIds.add(producer.getId()));
return this.infoProducers.isJobEnabled(job)
? new ConsumerJobStatus(ConsumerJobStatus.InfoJobStatusValues.ENABLED, producerIds)
: new ConsumerJobStatus(ConsumerJobStatus.InfoJobStatusValues.DISABLED, producerIds);
description = "Information subscription job is not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> deleteIndividualEiJob( //
+ public ResponseEntity<Object> deleteIndividualInfoJob( //
@PathVariable(ConsumerConsts.INFO_JOB_ID_PATH) String jobId) {
try {
logger.debug("DELETE info job, id: {}", jobId);
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))})
public Mono<ResponseEntity<Object>> putIndividualInfoJob( //
@PathVariable(ConsumerConsts.INFO_JOB_ID_PATH) String jobId, //
- @RequestBody ConsumerJobInfo informationJobObject) {
+ @RequestBody ConsumerJobInfo informationJobObject) throws ServiceException {
final boolean isNewJob = this.infoJobs.get(jobId) == null;
logger.debug("PUT info job, id: {}, obj: {}", jobId, informationJobObject);
+ InfoType infoType = this.infoTypes.getCompatibleType(informationJobObject.infoTypeId);
- return validatePutInfoJob(jobId, informationJobObject) //
- .flatMap(this::startInfoSubscriptionJob) //
+ return validatePutInfoJob(jobId, infoType, informationJobObject) //
+ .flatMap(job -> startInfoSubscriptionJob(job, infoType)) //
.doOnNext(this.infoJobs::put) //
- .map(newEiJob -> new ResponseEntity<>(isNewJob ? HttpStatus.CREATED : HttpStatus.OK)) //
+ .map(newJob -> new ResponseEntity<>(isNewJob ? HttpStatus.CREATED : HttpStatus.OK)) //
.onErrorResume(throwable -> Mono.just(ErrorResponse.create(throwable, HttpStatus.NOT_FOUND)));
}
.callbackUrl(s.statusResultUri).build();
}
- private Mono<InfoJob> startInfoSubscriptionJob(InfoJob newInfoJob) {
- return this.producerCallbacks.startInfoSubscriptionJob(newInfoJob, infoProducers) //
+ private Mono<InfoJob> startInfoSubscriptionJob(InfoJob newInfoJob, InfoType type) {
+ return this.producerCallbacks.startInfoSubscriptionJob(newInfoJob, type, infoProducers) //
.doOnNext(noOfAcceptingProducers -> this.logger.debug("Started job {}, number of activated producers: {}",
newInfoJob.getId(), noOfAcceptingProducers)) //
.map(noOfAcceptingProducers -> newInfoJob);
}
- private Mono<InfoJob> validatePutInfoJob(String jobId, ConsumerJobInfo jobInfo) {
+ private Mono<InfoJob> validatePutInfoJob(String jobId, InfoType infoType, ConsumerJobInfo jobInfo) {
try {
-
- InfoType infoType = this.infoTypes.getType(jobInfo.infoTypeId);
validateJsonObjectAgainstSchema(infoType.getJobDataSchema(), jobInfo.jobDefinition);
-
- InfoJob existingEiJob = this.infoJobs.get(jobId);
validateUri(jobInfo.statusNotificationUri);
validateUri(jobInfo.jobResultUri);
- if (existingEiJob != null && !existingEiJob.getTypeId().equals(jobInfo.infoTypeId)) {
- throw new ServiceException("Not allowed to change type for existing job", HttpStatus.CONFLICT);
+ InfoJob existingJob = this.infoJobs.get(jobId);
+ if (existingJob != null) {
+ InfoType.TypeId typeId = InfoType.TypeId.ofString(jobInfo.infoTypeId);
+ if (!existingJob.getType().getId().contains(typeId.getName())) {
+ throw new ServiceException("Not allowed to change type for existing job", HttpStatus.CONFLICT);
+ }
}
- return Mono.just(toEiJob(jobInfo, jobId, jobInfo.infoTypeId));
+
+ return Mono.just(toInfoJob(jobInfo, jobId, infoType));
} catch (Exception e) {
return Mono.error(e);
}
}
}
- private InfoJob toEiJob(ConsumerJobInfo info, String id, String typeId) {
+ private InfoJob toInfoJob(ConsumerJobInfo info, String id, InfoType type) {
return InfoJob.builder() //
.id(id) //
- .typeId(typeId) //
+ .type(type) //
.owner(info.owner) //
.jobData(info.jobDefinition) //
.targetUrl(info.jobResultUri) //
private ConsumerInfoTypeInfo toInfoTypeInfo(InfoType type) {
return new ConsumerInfoTypeInfo(type.getJobDataSchema(), typeStatus(type),
- this.infoProducers.getProducerIdsForType(type.getId()).size());
+ this.infoProducers.getProducerIdsForType(type).size());
}
private ConsumerInfoTypeInfo.ConsumerTypeStatusValues typeStatus(InfoType type) {
- for (InfoProducer producer : this.infoProducers.getProducersForType(type)) {
+ for (InfoProducer producer : this.infoProducers.getProducersSupportingType(type)) {
if (producer.isAvailable()) {
return ConsumerInfoTypeInfo.ConsumerTypeStatusValues.ENABLED;
}
return ConsumerInfoTypeInfo.ConsumerTypeStatusValues.DISABLED;
}
- private ConsumerJobInfo toInfoJobInfo(InfoJob s) {
- return new ConsumerJobInfo(s.getTypeId(), s.getJobData(), s.getOwner(), s.getTargetUrl(), s.getJobStatusUrl());
+ private ConsumerJobInfo toInfoJobInfo(InfoJob j) {
+ return new ConsumerJobInfo(j.getType().getId(), j.getJobData(), j.getOwner(), j.getTargetUrl(),
+ j.getJobStatusUrl());
}
}
import org.oransc.ics.repository.InfoJobs;
import org.oransc.ics.repository.InfoProducer;
import org.oransc.ics.repository.InfoProducers;
+import org.oransc.ics.repository.InfoType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
}
public void stopInfoJob(InfoJob infoJob, InfoProducers infoProducers) {
- for (InfoProducer producer : getProducersForJob(infoJob, infoProducers)) {
+ for (InfoProducer producer : getProducersForJob(infoJob.getType(), infoProducers)) {
String url = producer.getJobCallbackUrl() + "/" + infoJob.getId();
producer.setJobDisabled(infoJob);
restClient.delete(url) //
* @param infoJob an Information Job
* @return the number of producers that returned OK
*/
- public Mono<Integer> startInfoSubscriptionJob(InfoJob infoJob, InfoProducers infoProducers) {
+ public Mono<Integer> startInfoSubscriptionJob(InfoJob infoJob, InfoType type, InfoProducers infoProducers) {
Retry retrySpec = Retry.fixedDelay(1, Duration.ofSeconds(1));
- return Flux.fromIterable(getProducersForJob(infoJob, infoProducers)) //
+ return Flux.fromIterable(getProducersForJob(type, infoProducers)) //
.flatMap(infoProducer -> startInfoJob(infoProducer, infoJob, retrySpec)) //
.collectList() //
.map(okResponses -> Integer.valueOf(okResponses.size())); //
.doOnNext(resp -> producer.setJobEnabled(infoJob));
}
- private Collection<InfoProducer> getProducersForJob(InfoJob infoJob, InfoProducers infoProducers) {
- return infoProducers.getProducersForType(infoJob.getTypeId());
+ private Collection<InfoProducer> getProducersForJob(InfoType type, InfoProducers infoProducers) {
+ return infoProducers.getProducersSupportingType(type);
}
}
public static final String INFO_PRODUCER_ID_PATH = "infoProducerId";
+ public static final String DELETE_INFO_TYPE_DESCRPTION = "Existing jobs of the type will be automatically deleted.";
+
private ProducerConsts() {
}
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.oransc.ics.controllers.ErrorResponse;
import org.oransc.ics.controllers.VoidResponse;
import org.oransc.ics.repository.InfoJob;
import org.oransc.ics.repository.InfoJobs;
import org.oransc.ics.repository.InfoProducer;
+import org.oransc.ics.repository.InfoProducerRegistrationInfo;
import org.oransc.ics.repository.InfoProducers;
import org.oransc.ics.repository.InfoType;
import org.oransc.ics.repository.InfoTypeSubscriptions;
@DeleteMapping(
path = ProducerConsts.API_ROOT + "/info-types/{infoTypeId}",
produces = MediaType.APPLICATION_JSON_VALUE) //
- @Operation(summary = "Individual Information Type", description = "") //
+ @Operation(summary = "Individual Information Type", description = ProducerConsts.DELETE_INFO_TYPE_DESCRPTION) //
@ApiResponses(
value = { //
@ApiResponse(
if (type == null) {
return ErrorResponse.create("Information type not found", HttpStatus.NOT_FOUND);
}
- if (!this.infoProducers.getProducersForType(type).isEmpty()) {
- String firstProducerId = this.infoProducers.getProducersForType(type).iterator().next().getId();
+ if (!this.infoProducers.getProducersSupportingType(type).isEmpty()) {
+ String firstProducerId = this.infoProducers.getProducersSupportingType(type).iterator().next().getId();
return ErrorResponse.create("The type has active producers: " + firstProducerId, HttpStatus.CONFLICT);
}
this.infoTypes.remove(type);
- infoJobs.getJobsForType(type).forEach(job -> infoJobs.remove(job, infoProducers)); // Delete jobs for the type
+ infoJobs.getJobsForType(type).forEach(job -> infoJobs.remove(job, infoProducers)); // Delete jobs for the
+ // type
this.typeSubscriptions.notifyTypeRemoved(type);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Parameter(
name = ProducerConsts.INFO_TYPE_ID_PARAM,
required = false,
- description = "If given, only the producers for the EI Data type is returned.") //
+ description = "If given, only the producers for the Info Type is returned.") //
@RequestParam(name = ProducerConsts.INFO_TYPE_ID_PARAM, required = false) String typeId //
- ) {
+ ) throws ServiceException {
logger.debug("GET producer identifiers");
List<String> result = new ArrayList<>();
- for (InfoProducer infoProducer : typeId == null ? this.infoProducers.getAllProducers()
- : this.infoProducers.getProducersForType(typeId)) {
+ for (InfoProducer infoProducer : getProducers(typeId)) {
result.add(infoProducer.getId());
}
return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
}
+ private Collection<InfoProducer> getProducers(String typeId) throws ServiceException {
+ if (typeId == null) {
+ return this.infoProducers.getAllProducers();
+ }
+ InfoType type = infoTypes.get(typeId);
+ if (type == null) {
+ return new ArrayList<>();
+ }
+ return infoProducers.getProducersSupportingType(infoTypes.getType(typeId));
+ }
+
@GetMapping(
path = ProducerConsts.API_ROOT + "/info-producers/{infoProducerId}",
produces = MediaType.APPLICATION_JSON_VALUE)
return new ProducerInfoTypeInfo(t.getJobDataSchema(), t.getTypeSpecificInfo());
}
- private InfoProducers.InfoProducerRegistrationInfo toProducerRegistrationInfo(String infoProducerId,
+ private InfoProducerRegistrationInfo toProducerRegistrationInfo(String infoProducerId,
ProducerRegistrationInfo info) throws ServiceException {
- Collection<InfoType> supportedTypes = new ArrayList<>();
+ Set<InfoType> supportedTypes = new HashSet<>();
for (String typeId : info.supportedTypeIds) {
InfoType type = this.infoTypes.getType(typeId);
supportedTypes.add(type);
}
- return InfoProducers.InfoProducerRegistrationInfo.builder() //
+ return InfoProducerRegistrationInfo.builder() //
.id(infoProducerId) //
.jobCallbackUrl(info.jobCallbackUrl) //
.producerSupervisionCallbackUrl(info.producerSupervisionCallbackUrl) //
}
public ProducerJobInfo(InfoJob job) {
- this(job.getJobData(), job.getId(), job.getTypeId(), job.getTargetUrl(), job.getOwner(), job.getLastUpdated());
+ this(job.getJobData(), job.getId(), job.getType().getId(), job.getTargetUrl(), job.getOwner(),
+ job.getLastUpdated());
}
public ProducerJobInfo() {
import java.time.Instant;
import lombok.Builder;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.slf4j.Logger;
private final String id;
@Getter
- private final String typeId;
+ private final InfoType type;
@Getter
private final String owner;
@Builder.Default
private boolean isLastStatusReportedEnabled = true;
+ @Getter
+ @Builder
+ @EqualsAndHashCode
+ public static class PersistentData {
+ private String id;
+ private String typeId;
+ private String owner;
+ private Object jobData;
+ private String targetUrl;
+ private String jobStatusUrl;
+ private String lastUpdated;
+ }
+
public void setLastReportedStatus(boolean isEnabled) {
this.isLastStatusReportedEnabled = isEnabled;
logger.debug("Job status id: {}, enabled: {}", this.isLastStatusReportedEnabled, isEnabled);
}
+ public PersistentData getPersistentData() {
+ return PersistentData.builder() //
+ .id(id) //
+ .jobData(jobData) //
+ .jobStatusUrl(jobStatusUrl) //
+ .owner(owner) //
+ .targetUrl(targetUrl) //
+ .typeId(type.getId()) //
+ .lastUpdated(lastUpdated) //
+ .build();
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof InfoJob) {
+ return this.id.equals(((InfoJob) o).id);
+ }
+ return this.id.equals(o);
+ }
+
}
public class InfoJobs {
private Map<String, InfoJob> allEiJobs = new HashMap<>();
- private MultiMap<InfoJob> jobsByType = new MultiMap<>();
- private MultiMap<InfoJob> jobsByOwner = new MultiMap<>();
+ private MultiMap<String, InfoJob> jobsByType = new MultiMap<>();
+ private MultiMap<String, InfoJob> jobsByOwner = new MultiMap<>();
private final Gson gson;
+ private final InfoTypes infoTypes;
private final ApplicationConfig config;
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final ProducerCallbacks producerCallbacks;
- public InfoJobs(ApplicationConfig config, ProducerCallbacks producerCallbacks) {
+ public InfoJobs(ApplicationConfig config, InfoTypes infoTypes, ProducerCallbacks producerCallbacks) {
this.config = config;
GsonBuilder gsonBuilder = new GsonBuilder();
ServiceLoader.load(TypeAdapterFactory.class).forEach(gsonBuilder::registerTypeAdapterFactory);
this.gson = gsonBuilder.create();
this.producerCallbacks = producerCallbacks;
+ this.infoTypes = infoTypes;
}
public synchronized void restoreJobsFromDatabase() throws IOException {
for (File file : dbDir.listFiles()) {
String json = Files.readString(file.toPath());
- InfoJob job = gson.fromJson(json, InfoJob.class);
- this.doPut(job);
+ InfoJob.PersistentData data = gson.fromJson(json, InfoJob.PersistentData.class);
+ try {
+ InfoJob job = toInfoJob(data);
+ this.doPut(job);
+ } catch (ServiceException e) {
+ logger.warn("Could not restore job:{},reason: {}", data.getId(), e.getMessage());
+ }
}
}
+ private InfoJob toInfoJob(InfoJob.PersistentData data) throws ServiceException {
+ InfoType type = infoTypes.getType(data.getTypeId());
+ return InfoJob.builder() //
+ .id(data.getId()) //
+ .type(type) //
+ .owner(data.getOwner()) //
+ .jobData(data.getJobData()) //
+ .targetUrl(data.getTargetUrl()) //
+ .jobStatusUrl(data.getJobStatusUrl()) //
+ .lastUpdated(data.getLastUpdated()) //
+ .build();
+ }
+
public synchronized void put(InfoJob job) {
this.doPut(job);
storeJobInFile(job);
public synchronized void remove(InfoJob job, InfoProducers infoProducers) {
this.allEiJobs.remove(job.getId());
- jobsByType.remove(job.getTypeId(), job.getId());
- jobsByOwner.remove(job.getOwner(), job.getId());
+ jobsByType.remove(job.getType().getId(), job);
+ jobsByOwner.remove(job.getOwner(), job);
try {
Files.delete(getPath(job));
logger.warn("Could not remove file: {}", e.getMessage());
}
this.producerCallbacks.stopInfoJob(job, infoProducers);
+
}
public synchronized int size() {
private void doPut(InfoJob job) {
allEiJobs.put(job.getId(), job);
- jobsByType.put(job.getTypeId(), job.getId(), job);
- jobsByOwner.put(job.getOwner(), job.getId(), job);
+ jobsByType.put(job.getType().getId(), job);
+ jobsByOwner.put(job.getOwner(), job);
}
private void storeJobInFile(InfoJob job) {
try {
try (PrintStream out = new PrintStream(new FileOutputStream(getFile(job)))) {
- out.print(gson.toJson(job));
+ out.print(gson.toJson(job.getPersistentData()));
}
} catch (Exception e) {
logger.warn("Could not store job: {} {}", job.getId(), e.getMessage());
package org.oransc.ics.repository;
-import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
private final String id;
@Getter
- private final Collection<InfoType> infoTypes;
+ private final Set<InfoType> infoTypes;
@Getter
private final String jobCallbackUrl;
private int unresponsiveCounter = 0;
- public InfoProducer(String id, Collection<InfoType> infoTypes, String jobCallbackUrl,
+ public InfoProducer(String id, Set<InfoType> infoTypes, String jobCallbackUrl,
String producerSupervisionCallbackUrl) {
this.id = id;
this.infoTypes = infoTypes;
return this.enabledJobs.contains(job.getId());
}
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof InfoProducer) {
+ return this.id.equals(((InfoProducer) o).id);
+ }
+ return this.id.equals(o);
+ }
+
}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2022 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.ics.repository;
+
+import java.util.Set;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class InfoProducerRegistrationInfo {
+ String id;
+
+ Set<InfoType> supportedTypes;
+
+ String jobCallbackUrl;
+
+ String producerSupervisionCallbackUrl;
+}
import java.util.Map;
import java.util.Vector;
-import lombok.Builder;
-import lombok.Getter;
-
import org.oransc.ics.controllers.a1e.A1eCallbacks;
import org.oransc.ics.controllers.r1producer.ProducerCallbacks;
import org.oransc.ics.exceptions.ServiceException;
@Component
public class InfoProducers {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private final Map<String, InfoProducer> allEiProducers = new HashMap<>();
- private final MultiMap<InfoProducer> producersByType = new MultiMap<>();
+ private final Map<String, InfoProducer> allInfoProducers = new HashMap<>();
@Autowired
private ProducerCallbacks producerCallbacks;
@Autowired
private InfoJobs infoJobs;
- @Builder
- @Getter
- public static class InfoProducerRegistrationInfo {
- String id;
-
- Collection<InfoType> supportedTypes;
-
- String jobCallbackUrl;
-
- String producerSupervisionCallbackUrl;
- }
+ @Autowired
+ private InfoTypes infoTypes;
public InfoProducer registerProducer(InfoProducerRegistrationInfo producerInfo) {
final String producerId = producerInfo.getId();
InfoProducer previousDefinition = this.get(producerId);
- if (previousDefinition != null) {
- for (InfoType type : previousDefinition.getInfoTypes()) {
- producersByType.remove(type.getId(), producerId);
- }
- allEiProducers.remove(producerId);
- }
InfoProducer producer = createProducer(producerInfo);
- allEiProducers.put(producer.getId(), producer);
- for (InfoType type : producer.getInfoTypes()) {
- producersByType.put(type.getId(), producer.getId(), producer);
- }
+ allInfoProducers.put(producer.getId(), producer);
Collection<InfoType> previousTypes =
previousDefinition != null ? previousDefinition.getInfoTypes() : new ArrayList<>();
}
public synchronized Collection<InfoProducer> getAllProducers() {
- return new Vector<>(allEiProducers.values());
+ return new Vector<>(allInfoProducers.values());
}
public synchronized InfoProducer getProducer(String id) throws ServiceException {
- InfoProducer p = allEiProducers.get(id);
+ InfoProducer p = allInfoProducers.get(id);
if (p == null) {
throw new ServiceException("Could not find Information Producer: " + id, HttpStatus.NOT_FOUND);
}
}
public synchronized InfoProducer get(String id) {
- return allEiProducers.get(id);
+ return allInfoProducers.get(id);
}
public synchronized int size() {
- return allEiProducers.size();
+ return allInfoProducers.size();
}
public synchronized void clear() {
- this.allEiProducers.clear();
- this.producersByType.clear();
+ this.allInfoProducers.clear();
}
public void deregisterProducer(InfoProducer producer) {
- allEiProducers.remove(producer.getId());
- for (InfoType type : producer.getInfoTypes()) {
- if (producersByType.remove(type.getId(), producer.getId()) == null) {
- this.logger.error("Bug, no producer found");
- }
- }
+ allInfoProducers.remove(producer.getId());
this.consumerCallbacks.notifyJobStatus(producer.getInfoTypes(), this) //
.subscribe();
- }
- public synchronized Collection<InfoProducer> getProducersForType(InfoType type) {
- return this.producersByType.get(type.getId());
}
- public synchronized Collection<InfoProducer> getProducersForType(String typeId) {
- return this.producersByType.get(typeId);
+ public synchronized Collection<InfoProducer> getProducersSupportingType(InfoType type) {
+ InfoType.TypeId id = type.getTypeId();
+ Collection<InfoProducer> result = new ArrayList<>();
+ for (InfoProducer producer : this.allInfoProducers.values()) {
+ if (producer.getInfoTypes().contains(type)
+ || !InfoType.getCompatibleTypes(producer.getInfoTypes(), id).isEmpty()) {
+ result.add(producer);
+ }
+ }
+
+ return result;
}
- public synchronized Collection<String> getProducerIdsForType(String typeId) {
+ public synchronized Collection<String> getProducerIdsForType(InfoType type) {
Collection<String> producerIds = new ArrayList<>();
- for (InfoProducer p : this.getProducersForType(typeId)) {
+ for (InfoProducer p : this.getProducersSupportingType(type)) {
producerIds.add(p.getId());
}
return producerIds;
}
public synchronized boolean isJobEnabled(InfoJob job) {
- for (InfoProducer producer : this.producersByType.get(job.getTypeId())) {
- if (producer.isJobEnabled(job)) {
- return true;
+ InfoType type;
+ try {
+ type = this.infoTypes.getType(job.getType().getId());
+
+ for (InfoProducer producer : this.getProducersSupportingType(type)) {
+ if (producer.isJobEnabled(job)) {
+ return true;
+ }
}
+ } catch (ServiceException e) {
+ logger.error("Unexpected execption: {}", e.getMessage());
}
return false;
}
package org.oransc.ics.repository;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
+import org.oransc.ics.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+
@ToString
public class InfoType {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Builder
+ @Getter
+ @EqualsAndHashCode
+ public static class PersistentInfo {
+ @Getter
+ private String id;
+
+ @Getter
+ private Object jobDataSchema;
+
+ @Getter
+ private Object typeSpecificInfo;
+ }
+
+ @Getter
+ @ToString
+ public static class Version {
+ public final int major;
+ public final int minor;
+ public final int patch;
+
+ public Version(int major, int minor, int patch) {
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ }
+
+ public static Version ofString(String version) throws ServiceException {
+ String[] versionTokenized = version.split("\\.");
+ if (versionTokenized.length != 3) {
+ throw new ServiceException("Version must contain major.minor.patch code: " + version,
+ HttpStatus.BAD_REQUEST);
+ }
+
+ try {
+ return new Version( //
+ Integer.parseInt(versionTokenized[0]), //
+ Integer.parseInt(versionTokenized[1]), //
+ Integer.parseInt(versionTokenized[2]) //
+ );
+ } catch (Exception e) {
+ throw new ServiceException("Syntax error in " + version, HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ public int compareTo(Version other) {
+ if (major != other.major)
+ return major - other.major;
+ if (minor != other.minor)
+ return minor - other.minor;
+ return patch - other.patch;
+ }
+
+ public boolean isCompatibleWith(Version other) {
+ return (major == other.major && minor >= other.minor);
+ }
+ }
+
+ @ToString
+ @Getter
+ public static class TypeId {
+ private final String name;
+ private final String version;
+
+ public TypeId(String name, String version) {
+ this.name = name;
+ this.version = version;
+ }
+
+ public static TypeId ofString(String typeId) {
+ StringBuilder name = new StringBuilder();
+ String version = "";
+ String[] tokens = typeId.split("_");
+
+ if (tokens.length >= 2) {
+ version = tokens[tokens.length - 1]; // Last token
+ for (int i = 0; i < tokens.length - 1; ++i) {
+ if (i != 0) {
+ name.append("_");
+ }
+ name.append(tokens[i]); // All other tokens
+ }
+ return new TypeId(name.toString(), version);
+ } else {
+ return new TypeId(typeId, "");
+ }
+ }
+ }
+
+ public static Collection<InfoType> getCompatibleTypes(Collection<InfoType> allTypes, TypeId requestedTypeId) {
+ List<InfoType> result = new ArrayList<>();
+ try {
+ InfoType.Version requestedVersion = InfoType.Version.ofString(requestedTypeId.version);
+ for (InfoType type : allTypes) {
+ TypeId typeId = type.getTypeId();
+ if (requestedTypeId.getName().equals(typeId.getName())
+ && InfoType.Version.ofString(typeId.getVersion()).isCompatibleWith(requestedVersion)) {
+ result.add(type);
+ }
+ }
+ result.sort((left, right) -> left.getVersion().compareTo(right.getVersion()));
+
+ } catch (ServiceException e) {
+ logger.warn("Failed to find compatible version with: {}, reason: {}", requestedTypeId, e.getMessage());
+ }
+ return result;
+ }
+
@Getter
private final String id;
this.typeSpecificInfo = typeSpecificInfo;
}
+ public InfoType(PersistentInfo info) {
+ this.id = info.getId();
+ this.jobDataSchema = info.getJobDataSchema();
+ this.typeSpecificInfo = info.getTypeSpecificInfo();
+ }
+
+ public PersistentInfo getPersistentInfo() {
+ return PersistentInfo.builder() //
+ .id(this.id) //
+ .jobDataSchema(this.jobDataSchema)//
+ .typeSpecificInfo(this.typeSpecificInfo) //
+ .build();
+ }
+
+ public TypeId getTypeId() {
+ return TypeId.ofString(getId());
+ }
+
+ public Version getVersion() {
+ try {
+ return Version.ofString(getTypeId().getVersion());
+ } catch (ServiceException e) {
+ logger.warn("Not possible to get version from info type ID {}, {}", getId(), e.getMessage());
+ return new Version(0, 0, 0);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof InfoType) {
+ return this.id.equals(((InfoType) o).id);
+ }
+ return this.id.equals(o);
+ }
+
}
public class InfoTypeSubscriptions {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map<String, SubscriptionInfo> allSubscriptions = new HashMap<>();
- private final MultiMap<SubscriptionInfo> subscriptionsByOwner = new MultiMap<>();
+ private final MultiMap<String, SubscriptionInfo> subscriptionsByOwner = new MultiMap<>();
private final Gson gson = new GsonBuilder().create();
private final ApplicationConfig config;
private final Map<String, ConsumerCallbackHandler> callbackHandlers = new HashMap<>();
private String owner;
private String apiVersion;
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SubscriptionInfo) {
+ return this.id.equals(((SubscriptionInfo) o).id);
+ }
+ return this.id.equals(o);
+ }
+
}
public InfoTypeSubscriptions(@Autowired ApplicationConfig config) {
/**
* Get a subscription and throw if not fond.
- *
+ *
* @param id the ID of the subscription to get.
* @return SubscriptionInfo
* @throws ServiceException if not found
/**
* Get a subscription or return null if not found. Equivalent to get in all java
* collections.
- *
+ *
* @param id the ID of the subscription to get.
* @return SubscriptionInfo
*/
public void remove(SubscriptionInfo subscription) {
allSubscriptions.remove(subscription.getId());
- subscriptionsByOwner.remove(subscription.owner, subscription.id);
+ subscriptionsByOwner.remove(subscription.owner, subscription);
try {
Files.delete(getPath(subscription));
/**
* Invoking one consumer. If the call fails after retries, the subscription is
* removed.
- *
+ *
* @param notifyFunc
* @param subscriptionInfo
* @return
private void doPut(SubscriptionInfo subscription) {
allSubscriptions.put(subscription.getId(), subscription);
- subscriptionsByOwner.put(subscription.owner, subscription.id, subscription);
+ subscriptionsByOwner.put(subscription.owner, subscription);
}
private File getFile(SubscriptionInfo subscription) {
@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
public class InfoTypes {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private final Map<String, InfoType> allEiTypes = new HashMap<>();
+ private final Map<String, InfoType> allInfoTypes = new HashMap<>();
private final ApplicationConfig config;
private final Gson gson;
for (File file : dbDir.listFiles()) {
String json = Files.readString(file.toPath());
- InfoType type = gson.fromJson(json, InfoType.class);
- allEiTypes.put(type.getId(), type);
+ InfoType.PersistentInfo storedData = gson.fromJson(json, InfoType.PersistentInfo.class);
+ InfoType type = new InfoType(storedData);
+ allInfoTypes.put(type.getId(), type);
}
}
public synchronized void put(InfoType type) {
- allEiTypes.put(type.getId(), type);
+ allInfoTypes.put(type.getId(), type);
storeInFile(type);
}
public synchronized Collection<InfoType> getAllInfoTypes() {
- return new Vector<>(allEiTypes.values());
+ return new Vector<>(allInfoTypes.values());
}
public synchronized InfoType getType(String id) throws ServiceException {
- InfoType type = allEiTypes.get(id);
+ InfoType type = allInfoTypes.get(id);
if (type == null) {
throw new ServiceException("Information type not found: " + id, HttpStatus.NOT_FOUND);
}
}
public synchronized InfoType get(String id) {
- return allEiTypes.get(id);
+ return allInfoTypes.get(id);
}
public synchronized void remove(InfoType type) {
- allEiTypes.remove(type.getId());
+ allInfoTypes.remove(type.getId());
try {
Files.delete(getPath(type));
} catch (IOException e) {
}
public synchronized int size() {
- return allEiTypes.size();
+ return allInfoTypes.size();
}
public synchronized void clear() {
- this.allEiTypes.clear();
+ this.allInfoTypes.clear();
clearDatabase();
}
+ public synchronized InfoType getCompatibleType(String typeId) throws ServiceException {
+ InfoType res = this.get(typeId);
+ if (res != null) {
+ return res;
+ }
+
+ Collection<InfoType> compatibleTypes =
+ InfoType.getCompatibleTypes(this.getAllInfoTypes(), InfoType.TypeId.ofString(typeId));
+ if (compatibleTypes.isEmpty()) {
+ throw new ServiceException("Information type not found: " + typeId, HttpStatus.NOT_FOUND);
+ }
+ return compatibleTypes.iterator().next();
+ }
+
private void clearDatabase() {
try {
FileSystemUtils.deleteRecursively(Path.of(getDatabaseDirectory()));
private void storeInFile(InfoType type) {
try {
try (PrintStream out = new PrintStream(new FileOutputStream(getFile(type)))) {
- out.print(gson.toJson(type));
+ out.print(gson.toJson(type.getPersistentInfo()));
}
} catch (Exception e) {
logger.warn("Could not save type: {} {}", type.getId(), e.getMessage());
package org.oransc.ics.repository;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
-import java.util.Vector;
+import java.util.Set;
/**
- * A map, where each key can be bound to may values (where each value has an own
- * ID)
+ * A map, where each key can be bound to may values
*/
-public class MultiMap<T> {
+public class MultiMap<K, V> {
- private final Map<String, Map<String, T>> map = new HashMap<>();
+ private final Map<K, Set<V>> map = new HashMap<>();
- public void put(String key, String id, T value) {
- this.map.computeIfAbsent(key, k -> new HashMap<>()).put(id, value);
+ public synchronized void put(K key, V value) {
+ this.map.computeIfAbsent(key, k -> new HashSet<>()).add(value);
}
- public T remove(String key, String id) {
- Map<String, T> innerMap = this.map.get(key);
+ public synchronized void remove(String key, V id) {
+ Set<V> innerMap = this.map.get(key);
if (innerMap != null) {
- T removedElement = innerMap.remove(id);
+ innerMap.remove(id);
if (innerMap.isEmpty()) {
this.map.remove(key);
}
- return removedElement;
}
- return null;
}
- public Collection<T> get(String key) {
- Map<String, T> innerMap = this.map.get(key);
+ public synchronized Collection<V> get(K key) {
+ Set<V> innerMap = this.map.get(key);
if (innerMap == null) {
return Collections.emptyList();
}
- return new Vector<>(innerMap.values());
+ Collection<V> result = new ArrayList<>(innerMap.size());
+ result.addAll(innerMap);
+ return result;
}
- public void clear() {
+ public synchronized void clear() {
this.map.clear();
}
class ApplicationTest {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private final String TYPE_ID = "typeId";
+ private final String TYPE_ID = "typeId_1.9.9";
private final String PRODUCER_ID = "producerId";
private final String EI_JOB_PROPERTY = "\"property1\"";
private final String EI_JOB_ID = "jobId";
assertThat(this.producerSimulator.getTestResults().jobsStarted).hasSize(3);
await().untilAsserted(() -> assertThat(this.producerSimulator.getTestResults().jobsStopped).hasSize(2));
-
}
@Test
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
putInfoJob(TYPE_ID, "jobId");
final String JOB_ID_JSON = "[\"jobId\"]";
- String url = A1eConsts.API_ROOT + "/eijobs?infoTypeId=typeId";
+ String url = A1eConsts.API_ROOT + "/eijobs?infoTypeId=" + TYPE_ID;
String rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
- url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
+ url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=" + TYPE_ID + "&&owner=owner";
rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
putInfoJob(TYPE_ID, "jobId");
final String JOB_ID_JSON = "[\"jobId\"]";
- String url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId";
+ String url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=" + TYPE_ID;
String rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
- url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId&&owner=owner";
+ url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=" + TYPE_ID + "&&owner=owner";
rsp = restClient().get(url).block();
assertThat(rsp).isEqualTo(JOB_ID_JSON);
@Test
void a1ePutEiJob() throws Exception {
+
// Test that one producer accepting a job is enough
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
String url = A1eConsts.API_ROOT + "/eijobs/jobId";
- String body = gson.toJson(infoJobInfo());
+ String body = gson.toJson(eiJobInfo());
ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
assertThat(this.infoJobs.size()).isEqualTo(1);
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
verifyJobStatus(EI_JOB_ID, "ENABLED");
}
+ @Test
+ void a1ePutEiJobVersionHandling() throws Exception {
+ final String REG_TYPE_ID1 = "type_1.5.0"; // Compatible
+ final String REG_TYPE_ID2 = "type_1.6.0"; // Compatible
+ final String REG_TYPE_ID3 = "type_2.0.0";
+ final String REG_TYPE_ID4 = "type_1.0.0";
+ final String REG_TYPE_ID5 = "xxxx_1.3.0"; // Other type
+
+ final String PUT_TYPE_ID = "type_1.1.0";
+ // Test that one producer accepting a job is enough
+ putInfoProducerWithOneType(REG_TYPE_ID1, REG_TYPE_ID1);
+ putInfoProducerWithOneType(REG_TYPE_ID2, REG_TYPE_ID2);
+ putInfoProducerWithOneType(REG_TYPE_ID3, REG_TYPE_ID3);
+ putInfoProducerWithOneType(REG_TYPE_ID4, REG_TYPE_ID4);
+ putInfoProducerWithOneType(REG_TYPE_ID5, REG_TYPE_ID5);
+
+ String url = A1eConsts.API_ROOT + "/eijobs/jobId";
+ String body = gson.toJson(eiJobInfo(PUT_TYPE_ID, EI_JOB_ID));
+ ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
+ assertThat(this.infoJobs.size()).isEqualTo(1);
+ assertThat(this.infoJobs.getJobs().iterator().next().getType().getId()).isEqualTo(REG_TYPE_ID1);
+ assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+
+ resp = restClient().putForEntity(url, body).block();
+ assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
+ }
+
@Test
void consumerPutInformationJob() throws Exception {
// Test that one producer accepting a job is enough
verifyJobStatus(EI_JOB_ID, "ENABLED");
}
+ @Test
+ void testVersioning() throws Exception {
+ final String REG_TYPE_ID1 = "type_1.5.0"; // Compatible
+ final String REG_TYPE_ID2 = "type_1.6.0"; // Compatible
+ final String REG_TYPE_ID3 = "type_2.0.0";
+ final String REG_TYPE_ID4 = "type_1.0.0";
+ final String REG_TYPE_ID5 = "xxxx_1.3.0"; // Other type
+
+ final String PUT_TYPE_ID = "type_1.1.0";
+ // Test that one producer accepting a job is enough
+ putInfoProducerWithOneType(REG_TYPE_ID1, REG_TYPE_ID1);
+ putInfoProducerWithOneType(REG_TYPE_ID2, REG_TYPE_ID2);
+ putInfoProducerWithOneType(REG_TYPE_ID3, REG_TYPE_ID3);
+ putInfoProducerWithOneType(REG_TYPE_ID4, REG_TYPE_ID4);
+ putInfoProducerWithOneType(REG_TYPE_ID5, REG_TYPE_ID5);
+
+ String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
+ String body = gson.toJson(consumerJobInfo(PUT_TYPE_ID, "jobId"));
+ ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
+ assertThat(this.infoJobs.size()).isEqualTo(1);
+ assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+
+ ConsumerJobInfo getInfo = gson.fromJson(restClient().get(url).block(), ConsumerJobInfo.class);
+ assertThat(getInfo.infoTypeId).isEqualTo(REG_TYPE_ID1);
+
+ ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
+ await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted).hasSize(2));
+ ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
+ assertThat(request.id).isEqualTo("jobId");
+
+ // TBD the producer gets the registerred type, but could get the requested
+ // (PUT_TYPE_ID). This
+ // depends on the
+ // the principles for backwards compability.
+ assertThat(request.typeId.equals(REG_TYPE_ID1) || request.typeId.equals(REG_TYPE_ID2)).isTrue();
+
+ verifyJobStatus(EI_JOB_ID, "ENABLED");
+
+ // Test update job
+ resp = restClient().putForEntity(url, body).block();
+ assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
+ await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted).hasSize(4));
+
+ }
+
@Test
void a1ePutEiJob_jsonSchemavalidationError() throws Exception {
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
String url = A1eConsts.API_ROOT + "/eijobs/jobId";
// The element with name "property1" is mandatory in the schema
- A1eEiJobInfo jobInfo = new A1eEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
- "targetUri", "jobStatusUrl");
+ A1eEiJobInfo jobInfo =
+ new A1eEiJobInfo(TYPE_ID, jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", "jobStatusUrl");
String body = gson.toJson(jobInfo);
testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "Json validation failure");
String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
// The element with name "property1" is mandatory in the schema
ConsumerJobInfo jobInfo =
- new ConsumerJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", null);
+ new ConsumerJobInfo(TYPE_ID, jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", null);
String body = gson.toJson(jobInfo);
testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "Json validation failure");
putInfoJob("typeId1", "jobId");
String url = A1eConsts.API_ROOT + "/eijobs/jobId";
- String body = gson.toJson(infoJobInfo("typeId2", "jobId"));
+ String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
"Not allowed to change type for existing EI job");
}
}
@Test
- void producerPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
+ void producerPutType() throws JsonMappingException, JsonProcessingException, ServiceException {
assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.CREATED);
assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.OK);
}
@Test
- void producerPutEiType_noSchema() {
+ void producerPutType_noSchema() {
String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
String body = "{}";
testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
}
@Test
- void producerDeleteEiType() throws Exception {
+ void producerDeleteType() throws Exception {
putInfoType(TYPE_ID);
this.putInfoJob(TYPE_ID, "job1");
this.putInfoJob(TYPE_ID, "job2");
}
@Test
- void producerDeleteEiTypeExistingProducer() throws Exception {
+ void producerDeleteTypeExistingProducer() throws Exception {
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
testErrorCode(restClient().delete(url), HttpStatus.CONFLICT, "The type has active producers: " + PRODUCER_ID);
assertThat(this.infoTypes.size()).isEqualTo(1);
}
+ @Test
+ void producerDeleteTypeExistingJob() throws Exception {
+ putInfoType(TYPE_ID);
+ String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
+ putInfoJob(TYPE_ID, EI_JOB_ID);
+ restClient().delete(url).block();
+ assertThat(this.infoTypes.size()).isZero();
+
+ // The jobs are implicitly deleted
+ assertThat(this.infoJobs.size()).isZero();
+ }
+
@Test
void producerPutProducerWithOneType_rejecting() throws Exception {
putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
String url = A1eConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
- String body = gson.toJson(infoJobInfo());
+ String body = gson.toJson(eiJobInfo());
restClient().put(url, body).block();
ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(this.infoTypes.size()).isEqualTo(1);
- assertThat(this.infoProducers.getProducersForType(TYPE_ID)).hasSize(1);
+ InfoType type = this.infoTypes.getType(TYPE_ID);
+ assertThat(this.infoProducers.getProducersSupportingType(type)).hasSize(1);
assertThat(this.infoProducers.size()).isEqualTo(1);
assertThat(this.infoProducers.get("infoProducerId").getInfoTypes().iterator().next().getId())
.isEqualTo(TYPE_ID);
this.infoTypes.getType(TYPE_ID);
url = A1eConsts.API_ROOT + "/eijobs/jobId";
- body = gson.toJson(infoJobInfo());
+ body = gson.toJson(eiJobInfo());
restClient().putForEntity(url, body).block();
ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
assertThat(this.infoProducers.size()).isEqualTo(2);
InfoType type = this.infoTypes.getType(TYPE_ID);
- assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId");
- assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId2");
+ assertThat(this.infoProducers.getProducerIdsForType(type)).contains("infoProducerId");
+ assertThat(this.infoProducers.getProducerIdsForType(type)).contains("infoProducerId2");
putInfoJob(TYPE_ID, "jobId");
assertThat(this.infoJobs.size()).isEqualTo(1);
deleteInfoProducer("infoProducerId");
assertThat(this.infoProducers.size()).isEqualTo(1);
- assertThat(this.infoProducers.getProducerIdsForType(TYPE_ID)).doesNotContain("infoProducerId");
+ assertThat(this.infoProducers.getProducerIdsForType(type)).doesNotContain("infoProducerId");
verifyJobStatus("jobId", "ENABLED");
deleteInfoProducer("infoProducerId2");
}
@Test
- void testEiJobDatabase() throws Exception {
+ void testJobDatabasePersistence() throws Exception {
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
putInfoJob(TYPE_ID, "jobId1");
putInfoJob(TYPE_ID, "jobId2");
{
InfoJob savedJob = this.infoJobs.getJob("jobId1");
// Restore the jobs
- InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
+ InfoJobs jobs = new InfoJobs(this.applicationConfig, this.infoTypes, this.producerCallbacks);
jobs.restoreJobsFromDatabase();
assertThat(jobs.size()).isEqualTo(2);
InfoJob restoredJob = jobs.getJob("jobId1");
+ assertThat(restoredJob.getPersistentData()).isEqualTo(savedJob.getPersistentData());
assertThat(restoredJob.getId()).isEqualTo("jobId1");
assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
}
{
// Restore the jobs, no jobs in database
- InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
+ InfoJobs jobs = new InfoJobs(this.applicationConfig, this.infoTypes, this.producerCallbacks);
jobs.restoreJobsFromDatabase();
assertThat(jobs.size()).isZero();
}
}
@Test
- void testEiTypesDatabase() throws Exception {
+ void testTypesDatabasePersistence() throws Exception {
putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
+ InfoType savedType = this.infoTypes.getType(TYPE_ID);
assertThat(this.infoTypes.size()).isEqualTo(1);
{
// Restore the types
- InfoTypes types = new InfoTypes(this.applicationConfig);
- types.restoreTypesFromDatabase();
- assertThat(types.size()).isEqualTo(1);
+ InfoTypes restoredTypes = new InfoTypes(this.applicationConfig);
+ restoredTypes.restoreTypesFromDatabase();
+ InfoType restoredType = restoredTypes.getType(TYPE_ID);
+ assertThat(restoredType.getPersistentInfo()).isEqualTo(savedType.getPersistentInfo());
+ assertThat(restoredTypes.size()).isEqualTo(1);
}
{
// Restore the jobs, no jobs in database
- InfoTypes types = new InfoTypes(this.applicationConfig);
- types.clear();
- types.restoreTypesFromDatabase();
- assertThat(types.size()).isZero();
+ InfoTypes restoredTypes = new InfoTypes(this.applicationConfig);
+ restoredTypes.clear();
+ restoredTypes.restoreTypesFromDatabase();
+ assertThat(restoredTypes.size()).isZero();
}
logger.warn("Test removing a job when the db file is gone");
this.infoTypes.remove(this.infoTypes.getType(TYPE_ID));
baseUrl() + A1eCallbacksSimulatorController.getJobStatusUrl(infoJobId));
}
- private A1eEiJobInfo infoJobInfo() throws Exception {
- return infoJobInfo(TYPE_ID, EI_JOB_ID);
+ private A1eEiJobInfo eiJobInfo() throws Exception {
+ return eiJobInfo(TYPE_ID, EI_JOB_ID);
}
- A1eEiJobInfo infoJobInfo(String typeId, String infoJobId) throws Exception {
- return infoJobInfo(typeId, infoJobId, "owner");
+ A1eEiJobInfo eiJobInfo(String typeId, String infoJobId) throws Exception {
+ return eiJobInfo(typeId, infoJobId, "owner");
}
- A1eEiJobInfo infoJobInfo(String typeId, String infoJobId, String owner) throws Exception {
+ A1eEiJobInfo eiJobInfo(String typeId, String infoJobId, String owner) throws Exception {
return new A1eEiJobInfo(typeId, jsonObject(), owner, "https://junk.com",
baseUrl() + A1eCallbacksSimulatorController.getJobStatusUrl(infoJobId));
}
private InfoJob putInfoJob(String infoTypeId, String jobId, String owner) throws Exception {
String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
- String body = gson.toJson(infoJobInfo(infoTypeId, jobId, owner));
+ String body = gson.toJson(eiJobInfo(infoTypeId, jobId, owner));
restClient().putForEntity(url, body).block();
return this.infoJobs.getJob(jobId);
private InfoJob putInfoJob(String infoTypeId, String jobId) throws Exception {
String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
- String body = gson.toJson(infoJobInfo(infoTypeId, jobId));
+ String body = gson.toJson(eiJobInfo(infoTypeId, jobId));
restClient().putForEntity(url, body).block();
return this.infoJobs.getJob(jobId);
}
private void logHeaders(Map<String, String> headers) {
- logger.debug("Header begin");
- headers.forEach((key, value) -> logger.debug(" key: {}, value: {}", key, value));
- logger.debug("Header end");
+ logger.trace("Header begin");
+ headers.forEach((key, value) -> logger.trace(" key: {}, value: {}", key, value));
+ logger.trace("Header end");
}
}