Skip to content

Commit 2149351

Browse files
authored
Bug-fixes, transformation POST/PUT (MODHAADM-68) (#103)
- 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)
1 parent dc4bd7c commit 2149351

File tree

2 files changed

+93
-35
lines changed

2 files changed

+93
-35
lines changed

src/main/java/org/folio/harvesteradmin/dataaccess/LegacyHarvesterStorage.java

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import static org.folio.harvesteradmin.dataaccess.statics.RequestParameters.supportedGetRequestParameters;
1515
import static org.folio.okapi.common.HttpResponse.responseText;
1616

17+
import io.vertx.core.AsyncResult;
18+
import io.vertx.core.CompositeFuture;
1719
import io.vertx.core.Future;
1820
import io.vertx.core.Promise;
1921
import io.vertx.core.Vertx;
@@ -140,7 +142,7 @@ public Future<ProcessedHarvesterResponseGetById> getConfigRecordById(
140142
return promise.future();
141143
}
142144

143-
private Future<ProcessedHarvesterResponse> getConfigRecordByIdOrName(
145+
private Future<ProcessedHarvesterResponse> getUniqueConfigRecordByIdOrName(
144146
String harvesterPath, String id, String name) {
145147
Promise<ProcessedHarvesterResponse> promise = Promise.promise();
146148
if (id == null || id.isEmpty()) {
@@ -160,13 +162,13 @@ private Future<ProcessedHarvesterResponse> getConfigRecordByIdOrName(
160162
promise.complete(
161163
new ProcessedHarvesterResponseGetUniqueByName(
162164
recordsByName.jsonObject(),
163-
422,
165+
404,
164166
"Record with name \"" + name + "\" not found", recordsFoundByName));
165167
} else {
166168
promise.complete(
167169
new ProcessedHarvesterResponseGetUniqueByName(
168170
recordsByName.jsonObject(),
169-
422,
171+
404,
170172
"Found multiple records with name \"" + name + "\"", recordsFoundByName));
171173
}
172174
return promise.future();
@@ -249,7 +251,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
249251
final String transformationId = entity.getJsonObject("transformation").getString("id");
250252
final String storageName = entity.getJsonObject("storage").getString("name");
251253
final String transformationName = entity.getJsonObject("transformation").getString("name");
252-
getConfigRecordByIdOrName(HARVESTER_STORAGES_PATH, storageId, storageName)
254+
getUniqueConfigRecordByIdOrName(HARVESTER_STORAGES_PATH, storageId, storageName)
253255
.onComplete(storage -> {
254256
if (storage.succeeded()) {
255257
if (storage.result().wasOK()) {
@@ -265,7 +267,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
265267
fatalError.add("Error looking up storage by id or name "
266268
+ storage.cause().getMessage());
267269
}
268-
getConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH,
270+
getUniqueConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH,
269271
transformationId, transformationName)
270272
.onComplete(transformation -> {
271273
if (transformation.succeeded()) {
@@ -278,7 +280,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
278280
constraintViolation.add(transformation.result().errorMessage());
279281

280282
}
281-
if (constraintViolation.size() > 0) {
283+
if (!constraintViolation.isEmpty()) {
282284
promise.complete(
283285
new ProcessedHarvesterResponsePost(422, constraintViolation.toString())
284286
);
@@ -292,7 +294,7 @@ public Future<ProcessedHarvesterResponsePost> resolveReferencedEntities(
292294
+ transformation.cause().getMessage());
293295
}
294296
});
295-
if (fatalError.size() > 0) {
297+
if (!fatalError.isEmpty()) {
296298
promise.complete(new ProcessedHarvesterResponsePost(500,fatalError.toString()));
297299
}
298300
});
@@ -408,8 +410,12 @@ public Future<ProcessedHarvesterResponsePut> putConfigRecord(RoutingContext rout
408410
String harvesterPath = mapToHarvesterPath(routingContext);
409411
JsonObject jsonToPut = routingContext.body().asJsonObject();
410412
String id = routingContext.request().getParam("id");
411-
if (harvesterPath != null && harvesterPath.equals(HARVESTER_HARVESTABLES_PATH)) {
412-
jsonToPut.put("lastUpdated", iso_instant.format(Instant.now()));
413+
if (harvesterPath != null) {
414+
if (harvesterPath.equals(HARVESTER_TRANSFORMATIONS_PATH)) {
415+
return putTransformation(routingContext);
416+
} else if (harvesterPath.equals(HARVESTER_HARVESTABLES_PATH)) {
417+
jsonToPut.put("lastUpdated", iso_instant.format(Instant.now()));
418+
}
413419
}
414420
return putConfigRecord(routingContext, harvesterPath, jsonToPut, id);
415421
}
@@ -580,12 +586,41 @@ public Future<Integer> checkForReferencingEntities(String api, String id) {
580586
return promise.future();
581587
}
582588

583-
584-
585-
private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
586-
RoutingContext routingContext) {
589+
private Future<ProcessedHarvesterResponsePut> putTransformation(RoutingContext routingContext) {
587590
JsonObject transformationJson = routingContext.body().asJsonObject();
588-
logger.debug("About to POST-then-PUT " + transformationJson.encodePrettily());
591+
logger.debug("About to PUT " + transformationJson.encodePrettily());
592+
List<Future<ProcessedHarvesterResponse>> stepFutures = getStepLookupFutures(transformationJson);
593+
Promise<ProcessedHarvesterResponsePut> promise = Promise.promise();
594+
GenericCompositeFuture.all(stepFutures).onComplete(steps -> {
595+
if (steps.succeeded()) {
596+
boolean allStepsFound = true;
597+
for (int h = 0; h < steps.result().size(); h++) {
598+
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(h);
599+
if (stepResponse.statusCode() == NOT_FOUND) {
600+
allStepsFound = false;
601+
promise.complete(new ProcessedHarvesterResponsePut(422,
602+
"Uniquely referenced step not found, cannot store transformation pipeline: "
603+
+ stepResponse.errorMessage()));
604+
break;
605+
}
606+
}
607+
if (allStepsFound) {
608+
expandAssociatedSteps(steps, transformationJson);
609+
putConfigRecord(
610+
routingContext,
611+
HARVESTER_TRANSFORMATIONS_PATH,
612+
transformationJson,
613+
transformationJson.getString("id")).onComplete(response ->
614+
promise.complete(response.result())
615+
);
616+
}
617+
}
618+
});
619+
return promise.future();
620+
}
621+
622+
private List<Future<ProcessedHarvesterResponse>> getStepLookupFutures(
623+
JsonObject transformationJson) {
589624
JsonArray stepsIdsJson =
590625
transformationJson.containsKey("stepAssociations") ? transformationJson.getJsonArray(
591626
"stepAssociations").copy() : new JsonArray();
@@ -599,19 +634,32 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
599634
String stepName = step.containsKey("step")
600635
? step.getJsonObject("step").getString("name")
601636
: step.getString("stepName");
602-
stepFutures.add(getConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName));
637+
stepFutures.add(getUniqueConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName));
603638
}
639+
return stepFutures;
640+
}
641+
642+
/**
643+
* Checks that referenced steps exist, POSTs the transformation without the steps,
644+
* creates schema compliant step associations in the transformation object,
645+
* PUTs the transformation, checks that a transformation with the given ID exists.
646+
* @return response structure
647+
*/
648+
private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
649+
RoutingContext routingContext) {
650+
JsonObject transformationJson = routingContext.body().asJsonObject();
651+
logger.debug("About to POST-then-PUT " + transformationJson.encodePrettily());
652+
List<Future<ProcessedHarvesterResponse>> stepFutures = getStepLookupFutures(transformationJson);
604653
Promise<ProcessedHarvesterResponsePost> promise = Promise.promise();
605654
GenericCompositeFuture.all(stepFutures).onComplete(steps -> {
606655
if (steps.succeeded()) {
607656
boolean allStepsFound = true;
608657
for (int h = 0; h < steps.result().size(); h++) {
609-
ProcessedHarvesterResponseGetById stepResponse = steps.result().resultAt(h);
658+
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(h);
610659
if (stepResponse.statusCode() == NOT_FOUND) {
611-
logger.info("Step not found: " + stepResponse.errorMessage());
612660
allStepsFound = false;
613661
promise.complete(new ProcessedHarvesterResponsePost(422,
614-
"Referenced step not found, cannot store transformation pipeline: "
662+
"Uniquely referenced step not found, cannot store transformation pipeline: "
615663
+ stepResponse.errorMessage()));
616664
break;
617665
}
@@ -625,21 +673,7 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
625673
if (transformationPost.succeeded()
626674
&& transformationPost.result().statusCode() == CREATED) {
627675
JsonObject createdTransformation = transformationPost.result().jsonObject();
628-
createdTransformation.put("stepAssociations", new JsonArray());
629-
for (int i = 0; i < steps.result().size(); i++) {
630-
ProcessedHarvesterResponseGetById stepResponse = steps.result().resultAt(i);
631-
final JsonObject stepJson = stepResponse.jsonObject();
632-
JsonObject tsaJson = new JsonObject();
633-
tsaJson.put("id", getRandomFifteenDigitString());
634-
tsaJson.put("position", Integer.toString(i + 1));
635-
tsaJson.put("step", new JsonObject());
636-
tsaJson.getJsonObject("step")
637-
.put("entityType",
638-
typeToEmbeddedTypeMap.get(stepJson.getString("type")));
639-
tsaJson.getJsonObject("step").put("id", stepJson.getString("id"));
640-
tsaJson.put("transformation", createdTransformation.getString("id"));
641-
createdTransformation.getJsonArray("stepAssociations").add(tsaJson);
642-
}
676+
expandAssociatedSteps(steps, createdTransformation);
643677
putConfigRecord(
644678
routingContext,
645679
HARVESTER_TRANSFORMATIONS_PATH,
@@ -687,6 +721,25 @@ private Future<ProcessedHarvesterResponsePost> doPostAndPutTransformation(
687721
return promise.future();
688722
}
689723

724+
private static void expandAssociatedSteps(AsyncResult<CompositeFuture> steps,
725+
JsonObject createdTransformation) {
726+
createdTransformation.put("stepAssociations", new JsonArray());
727+
for (int i = 0; i < steps.result().size(); i++) {
728+
ProcessedHarvesterResponse stepResponse = steps.result().resultAt(i);
729+
final JsonObject stepJson = stepResponse.jsonObject();
730+
JsonObject tsaJson = new JsonObject();
731+
tsaJson.put("id", getRandomFifteenDigitString());
732+
tsaJson.put("position", Integer.toString(i + 1));
733+
tsaJson.put("step", new JsonObject());
734+
tsaJson.getJsonObject("step")
735+
.put("entityType",
736+
typeToEmbeddedTypeMap.get(stepJson.getString("type")));
737+
tsaJson.getJsonObject("step").put("id", stepJson.getString("id"));
738+
tsaJson.put("transformation", createdTransformation.getString("id"));
739+
createdTransformation.getJsonArray("stepAssociations").add(tsaJson);
740+
}
741+
}
742+
690743
private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
691744
RoutingContext routingContext) {
692745
JsonObject incomingTsa = routingContext.body().asJsonObject();
@@ -695,7 +748,7 @@ private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
695748
String stepId = incomingTsa.getJsonObject("step").getString("id");
696749
String stepName = incomingTsa.getJsonObject("step").getString("name");
697750
Promise<ProcessedHarvesterResponsePost> promise = Promise.promise();
698-
getConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH, transId, transName).onComplete(
751+
getUniqueConfigRecordByIdOrName(HARVESTER_TRANSFORMATIONS_PATH, transId, transName).onComplete(
699752
theTransformation -> {
700753
if (theTransformation.failed()) {
701754
promise.complete(
@@ -710,7 +763,7 @@ private Future<ProcessedHarvesterResponsePost> doPostTsaPutTransformation(
710763
+ transId + " not found."));
711764
} else {
712765
JsonObject transformationFound = theTransformation.result().jsonObject();
713-
getConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName).onComplete(
766+
getUniqueConfigRecordByIdOrName(HARVESTER_STEPS_PATH, stepId, stepName).onComplete(
714767
theStep -> {
715768
if (!theStep.result().found()) {
716769
promise.complete(

src/main/resources/openapi/schemas/transformationPostPut.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
"type": "string",
77
"description": "Unique record identifier."
88
},
9+
"acl": {
10+
"type": "string",
11+
"description": "System controlled access control string.",
12+
"readOnly": true
13+
},
914
"name": {
1015
"type": "string",
1116
"description": "Name of the transformation pipeline."

0 commit comments

Comments
 (0)