Skip to content

Commit

Permalink
Bug-fixes, transformation POST/PUT (MODHAADM-68)
Browse files Browse the repository at this point in the history
 - allow (and ignore) property 'acl' for transformations like for other record types
 - fix class cast exception that prevents optional resolution of steps by unique name (instead of by ID)
 - resolve/expand associated steps from provided unique names or IDs to complete step association objects in compliance with the legacy API for transformation objects on PUTs (as already done on POSTs)
  • Loading branch information
nielserik committed Jan 10, 2024
1 parent 233b0f6 commit 001ad17
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import static org.folio.harvesteradmin.dataaccess.statics.RequestParameters.supportedGetRequestParameters;
import static org.folio.okapi.common.HttpResponse.responseText;

import io.vertx.core.AsyncResult;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
Expand Down Expand Up @@ -140,7 +142,7 @@ public Future<ProcessedHarvesterResponseGetById> getConfigRecordById(
return promise.future();
}

private Future<ProcessedHarvesterResponse> getConfigRecordByIdOrName(
private Future<ProcessedHarvesterResponse> getUniqueConfigRecordByIdOrName(
String harvesterPath, String id, String name) {
Promise<ProcessedHarvesterResponse> promise = Promise.promise();
if (id == null || id.isEmpty()) {
Expand All @@ -160,13 +162,13 @@ private Future<ProcessedHarvesterResponse> getConfigRecordByIdOrName(
promise.complete(
new ProcessedHarvesterResponseGetUniqueByName(
recordsByName.jsonObject(),
422,
404,
"Record with name \"" + name + "\" not found", recordsFoundByName));
} else {
promise.complete(
new ProcessedHarvesterResponseGetUniqueByName(
recordsByName.jsonObject(),
422,
404,
"Found multiple records with name \"" + name + "\"", recordsFoundByName));
}
return promise.future();
Expand Down Expand Up @@ -249,7 +251,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
final String transformationId = entity.getJsonObject("transformation").getString("id");
final String storageName = entity.getJsonObject("storage").getString("name");
final String transformationName = entity.getJsonObject("transformation").getString("name");
getConfigRecordByIdOrName(HARVESTER_STORAGES_PATH, storageId, storageName)
getUniqueConfigRecordByIdOrName(HARVESTER_STORAGES_PATH, storageId, storageName)
.onComplete(storage -> {
if (storage.succeeded()) {
if (storage.result().wasOK()) {
Expand All @@ -265,7 +267,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
fatalError.add("Error looking up storage by id or name "
+ storage.cause().getMessage());
}
getConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH,
getUniqueConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH,
transformationId, transformationName)
.onComplete(transformation -> {
if (transformation.succeeded()) {
Expand All @@ -278,7 +280,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
constraintViolation.add(transformation.result().errorMessage());

}
if (constraintViolation.size() > 0) {
if (!constraintViolation.isEmpty()) {
promise.complete(
new ProcessedHarvesterResponsePost(422, constraintViolation.toString())
);
Expand All @@ -292,7 +294,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
+ transformation.cause().getMessage());
}
});
if (fatalError.size() > 0) {
if (!fatalError.isEmpty()) {
promise.complete(new ProcessedHarvesterResponsePost(500,fatalError.toString()));
}
});
Expand Down Expand Up @@ -408,8 +410,12 @@ public Future<ProcessedHarvesterResponsePut> putConfigRecord(RoutingContext rout
String harvesterPath = mapToHarvesterPath(routingContext);
JsonObject jsonToPut = routingContext.body().asJsonObject();
String id = routingContext.request().getParam("id");
if (harvesterPath != null && harvesterPath.equals(HARVESTER_HARVESTABLES_PATH)) {
jsonToPut.put("lastUpdated", iso_instant.format(Instant.now()));
if (harvesterPath != null) {
if (harvesterPath.equals(HARVESTER_TRANSFORMATIONS_PATH)) {
return putTransformation(routingContext);
} else if (harvesterPath.equals(HARVESTER_HARVESTABLES_PATH)) {
jsonToPut.put("lastUpdated", iso_instant.format(Instant.now()));
}
}
return putConfigRecord(routingContext, harvesterPath, jsonToPut, id);
}
Expand Down Expand Up @@ -580,12 +586,41 @@ public Future<Integer> checkForReferencingEntities(String api, String id) {
return promise.future();
}



private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
RoutingContext routingContext) {
private Future<ProcessedHarvesterResponsePut> putTransformation(RoutingContext routingContext) {
JsonObject transformationJson = routingContext.body().asJsonObject();
logger.debug("About to POST-then-PUT " + transformationJson.encodePrettily());
logger.debug("About to PUT " + transformationJson.encodePrettily());
List<Future<ProcessedHarvesterResponse>> stepFutures = getStepLookupFutures(transformationJson);
Promise<ProcessedHarvesterResponsePut> promise = Promise.promise();
GenericCompositeFuture.all(stepFutures).onComplete(steps -> {
if (steps.succeeded()) {
boolean allStepsFound = true;
for (int h = 0; h < steps.result().size(); h++) {
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(h);
if (stepResponse.statusCode() == NOT_FOUND) {
allStepsFound = false;
promise.complete(new ProcessedHarvesterResponsePut(422,
"Uniquely referenced step not found, cannot store transformation pipeline: "
+ stepResponse.errorMessage()));
break;
}
}
if (allStepsFound) {
expandAssociatedSteps(steps, transformationJson);
putConfigRecord(
routingContext,
HARVESTER_TRANSFORMATIONS_PATH,
transformationJson,
transformationJson.getString("id")).onComplete(response ->
promise.complete(response.result())
);
}
}
});
return promise.future();
}

private List<Future<ProcessedHarvesterResponse>> getStepLookupFutures(
JsonObject transformationJson) {
JsonArray stepsIdsJson =
transformationJson.containsKey("stepAssociations") ? transformationJson.getJsonArray(
"stepAssociations").copy() : new JsonArray();
Expand All @@ -599,19 +634,32 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
String stepName = step.containsKey("step")
? step.getJsonObject("step").getString("name")
: step.getString("stepName");
stepFutures.add(getConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName));
stepFutures.add(getUniqueConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName));
}
return stepFutures;
}

/**
* Checks that referenced steps exist, POSTs the transformation without the steps,
* creates schema compliant step associations in the transformation object,
* PUTs the transformation, checks that a transformation with the given ID exists.
* @return response structure
*/
private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
RoutingContext routingContext) {
JsonObject transformationJson = routingContext.body().asJsonObject();
logger.debug("About to POST-then-PUT " + transformationJson.encodePrettily());
List<Future<ProcessedHarvesterResponse>> stepFutures = getStepLookupFutures(transformationJson);
Promise<ProcessedHarvesterResponsePost> promise = Promise.promise();
GenericCompositeFuture.all(stepFutures).onComplete(steps -> {
if (steps.succeeded()) {
boolean allStepsFound = true;
for (int h = 0; h < steps.result().size(); h++) {
ProcessedHarvesterResponseGetById stepResponse = steps.result().resultAt(h);
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(h);
if (stepResponse.statusCode() == NOT_FOUND) {
logger.info("Step not found: " + stepResponse.errorMessage());
allStepsFound = false;
promise.complete(new ProcessedHarvesterResponsePost(422,
"Referenced step not found, cannot store transformation pipeline: "
"Uniquely referenced step not found, cannot store transformation pipeline: "
+ stepResponse.errorMessage()));
break;
}
Expand All @@ -625,21 +673,7 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
if (transformationPost.succeeded()
&& transformationPost.result().statusCode() == CREATED) {
JsonObject createdTransformation = transformationPost.result().jsonObject();
createdTransformation.put("stepAssociations", new JsonArray());
for (int i = 0; i < steps.result().size(); i++) {
ProcessedHarvesterResponseGetById stepResponse = steps.result().resultAt(i);
final JsonObject stepJson = stepResponse.jsonObject();
JsonObject tsaJson = new JsonObject();
tsaJson.put("id", getRandomFifteenDigitString());
tsaJson.put("position", Integer.toString(i + 1));
tsaJson.put("step", new JsonObject());
tsaJson.getJsonObject("step")
.put("entityType",
typeToEmbeddedTypeMap.get(stepJson.getString("type")));
tsaJson.getJsonObject("step").put("id", stepJson.getString("id"));
tsaJson.put("transformation", createdTransformation.getString("id"));
createdTransformation.getJsonArray("stepAssociations").add(tsaJson);
}
expandAssociatedSteps(steps, createdTransformation);
putConfigRecord(
routingContext,
HARVESTER_TRANSFORMATIONS_PATH,
Expand Down Expand Up @@ -687,6 +721,25 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
return promise.future();
}

private static void expandAssociatedSteps(AsyncResult<CompositeFuture> steps,
JsonObject createdTransformation) {
createdTransformation.put("stepAssociations", new JsonArray());
for (int i = 0; i < steps.result().size(); i++) {
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(i);
final JsonObject stepJson = stepResponse.jsonObject();
JsonObject tsaJson = new JsonObject();
tsaJson.put("id", getRandomFifteenDigitString());
tsaJson.put("position", Integer.toString(i + 1));
tsaJson.put("step", new JsonObject());
tsaJson.getJsonObject("step")
.put("entityType",
typeToEmbeddedTypeMap.get(stepJson.getString("type")));
tsaJson.getJsonObject("step").put("id", stepJson.getString("id"));
tsaJson.put("transformation", createdTransformation.getString("id"));
createdTransformation.getJsonArray("stepAssociations").add(tsaJson);
}
}

private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
RoutingContext routingContext) {
JsonObject incomingTsa = routingContext.body().asJsonObject();
Expand All @@ -695,7 +748,7 @@ private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
String stepId = incomingTsa.getJsonObject("step").getString("id");
String stepName = incomingTsa.getJsonObject("step").getString("name");
Promise<ProcessedHarvesterResponsePost> promise = Promise.promise();
getConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH, transId, transName).onComplete(
getUniqueConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH, transId, transName).onComplete(
theTransformation -> {
if (theTransformation.failed()) {
promise.complete(
Expand All @@ -710,7 +763,7 @@ private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
+ transId + " not found."));
} else {
JsonObject transformationFound = theTransformation.result().jsonObject();
getConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName).onComplete(
getUniqueConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName).onComplete(
theStep -> {
if (!theStep.result().found()) {
promise.complete(
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/openapi/schemas/transformationPostPut.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"type": "string",
"description": "Unique record identifier."
},
"acl": {
"type": "string",
"description": "System controlled access control string.",
"readOnly": true
},
"name": {
"type": "string",
"description": "Name of the transformation pipeline."
Expand Down

0 comments on commit 001ad17

Please sign in to comment.