Skip to content

Commit

Permalink
Response with error message
Browse files Browse the repository at this point in the history
  • Loading branch information
RomanChernetskyi committed Jun 18, 2024
1 parent 0959ee2 commit b5765ae
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 95 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@
<path>${basedir}/ramls/mappingMetadataDto.json</path>
<path>${basedir}/ramls/holdings_update_ownership.json</path>
<path>${basedir}/ramls/items_update_ownership.json</path>
<path>${basedir}/ramls/move_response.json</path>
<path>${basedir}/ramls/update_ownership_response.json</path>
</sourcePaths>
<targetPackage>org.folio</targetPackage>
<generateBuilders>true</generateBuilders>
Expand Down
24 changes: 24 additions & 0 deletions ramls/update_ownership_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Holder for errors during holdings/item update ownership",
"type": "object",
"properties": {
"notUpdatedEntities": {
"description": "Items/holdings errors",
"type": "array",
"items": {
"type": "object",
"properties": {
"entityId": {
"$ref": "uuid.json"
},
"errorMessage": {
"type": "string",
"description": "Error message"
}
}
}
}
},
"additionalProperties": false
}
103 changes: 81 additions & 22 deletions src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.collections4.ListUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.HoldingsRecord;
import org.folio.HoldingsUpdateOwnership;
import org.folio.NotUpdatedEntity;
import org.folio.inventory.common.Context;
import org.folio.inventory.common.WebContext;
import org.folio.inventory.consortium.services.ConsortiumService;
import org.folio.inventory.domain.AsynchronousCollection;
import org.folio.inventory.domain.HoldingsRecordCollection;
import org.folio.inventory.domain.items.Item;
import org.folio.inventory.domain.items.ItemCollection;
Expand All @@ -25,8 +26,10 @@
import org.folio.inventory.support.MoveApiUtil;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

import static org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil.constructContext;
import static org.folio.inventory.domain.instances.InstanceSource.CONSORTIUM_FOLIO;
Expand All @@ -46,6 +49,8 @@ public class UpdateOwnershipApi extends AbstractInventoryResource {
public static final String INSTANCE_NOT_SHARED = "Instance with id: %s is not shared";
public static final String INSTANCE_NOT_FOUND_AT_SOURCE_TENANT = "Instance with id: %s not found at source tenant, tenant: %s";
public static final String TENANT_NOT_IN_CONSORTIA = "%s tenant is not in consortia";
public static final String INSTANCE_NOT_FOUND = "Instance with id: %s not found on tenant: %s";

private final ConsortiumService consortiumService;

public UpdateOwnershipApi(Storage storage, HttpClient client, ConsortiumService consortiumService) {
Expand Down Expand Up @@ -74,6 +79,8 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) {
}
var holdingsUpdateOwnership = updateOwnershipRequest.mapTo(HoldingsUpdateOwnership.class);

List<NotUpdatedEntity> notUpdatedEntities = new ArrayList<>();

LOGGER.info("updateHoldingsOwnership:: Started updating ownership of holdings record: {}, to tenant: {}", holdingsUpdateOwnership.getHoldingsRecordIds(),
holdingsUpdateOwnership.getTargetTenantId());

Expand All @@ -86,7 +93,7 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) {
if (instance != null) {
if (instance.getSource().equals(CONSORTIUM_MARC.getValue()) || instance.getSource().equals(CONSORTIUM_FOLIO.getValue())) {
Context targetTenantContext = constructContext(holdingsUpdateOwnership.getTargetTenantId(), context.getToken(), context.getOkapiLocation());
return updateOwnershipOfHoldingsRecords(holdingsUpdateOwnership, routingContext, context, targetTenantContext);
return updateOwnershipOfHoldingsRecords(holdingsUpdateOwnership, notUpdatedEntities, routingContext, context, targetTenantContext);
} else {
String instanceNotSharedErrorMessage = String.format(INSTANCE_NOT_SHARED, holdingsUpdateOwnership.getToInstanceId());
LOGGER.warn("updateHoldingsOwnership:: " + instanceNotSharedErrorMessage);
Expand All @@ -103,7 +110,7 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) {
LOGGER.warn("updateHoldingsOwnership:: " + notInConsortiaErrorMessage, context);
return CompletableFuture.failedFuture(new BadRequestException(notInConsortiaErrorMessage));
})
.thenAccept(updateHoldingsRecords -> respond(routingContext, holdingsUpdateOwnership.getHoldingsRecordIds(), updateHoldingsRecords))
.thenAccept(updateHoldingsRecords -> respond(routingContext, notUpdatedEntities))
.exceptionally(throwable -> {
LOGGER.warn("updateHoldingsOwnership:: Error during update ownership of holdings {}, to tenant: {}",
holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), throwable);
Expand All @@ -120,7 +127,8 @@ private void processUpdateItemsOwnership(RoutingContext routingContext) {
// should be implemented in MODINV-955
}

private CompletableFuture<List<String>> updateOwnershipOfHoldingsRecords(HoldingsUpdateOwnership holdingsUpdateOwnership, RoutingContext routingContext,
private CompletableFuture<List<String>> updateOwnershipOfHoldingsRecords(HoldingsUpdateOwnership holdingsUpdateOwnership,
List<NotUpdatedEntity> notUpdatedEntities, RoutingContext routingContext,
WebContext context, Context targetTenantContext) {
try {
CollectionResourceClient holdingsStorageClient = createHoldingsStorageClient(createHttpClient(client, routingContext, context),
Expand All @@ -131,20 +139,26 @@ private CompletableFuture<List<String>> updateOwnershipOfHoldingsRecords(Holding
HoldingsRecordCollection targetTenantHoldingsRecordCollection = storage.getHoldingsRecordCollection(targetTenantContext);

return holdingsRecordFetchClient.find(holdingsUpdateOwnership.getHoldingsRecordIds(), MoveApiUtil::fetchByIdCql)
.thenCompose(jsons -> createHoldings(jsons, holdingsUpdateOwnership.getToInstanceId(), targetTenantHoldingsRecordCollection))
.thenApply(createdHoldings -> createdHoldings.stream().map(HoldingsRecord::getId).toList())
.thenCompose(createdHoldingsIds ->
transferAttachedItems(createdHoldingsIds, routingContext, context, targetTenantContext)
.thenCompose(items -> deleteCollectionItems(createdHoldingsIds, sourceTenantHoldingsRecordCollection)));
.thenCompose(jsons -> {
processNotFoundedInstances(holdingsUpdateOwnership.getHoldingsRecordIds(), notUpdatedEntities, context, jsons);
return createHoldings(jsons, notUpdatedEntities, holdingsUpdateOwnership.getToInstanceId(), targetTenantHoldingsRecordCollection);
})
.thenCompose(createdHoldings -> {
List<String> createdHoldingsIds = createdHoldings.stream().map(HoldingsRecord::getId).toList();

return transferAttachedItems(createdHoldingsIds, notUpdatedEntities, routingContext, context, targetTenantContext)
.thenCompose(itemIds ->
deleteHoldings(getHoldingsToDelete(notUpdatedEntities, createdHoldings), notUpdatedEntities, sourceTenantHoldingsRecordCollection));
});
} catch (Exception e) {
LOGGER.warn("updateOwnershipOfHoldingsRecords:: Error during update ownership of holdings {}, to tenant: {}",
holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), e);
return CompletableFuture.failedFuture(e);
}
}

private CompletableFuture<List<String>> transferAttachedItems(List<String> holdingsRecordIds, RoutingContext routingContext,
WebContext context, Context targetTenantContext) {
private CompletableFuture<List<String>> transferAttachedItems(List<String> holdingsRecordIds, List<NotUpdatedEntity> notUpdatedEntities,
RoutingContext routingContext, WebContext context, Context targetTenantContext) {
try {
CollectionResourceClient itemsStorageClient = createItemStorageClient(createHttpClient(client, routingContext, context), context);
MultipleRecordsFetchClient itemsFetchClient = createItemsFetchClient(itemsStorageClient);
Expand All @@ -153,23 +167,27 @@ private CompletableFuture<List<String>> transferAttachedItems(List<String> holdi
ItemCollection targetTenantItemCollection = storage.getItemCollection(targetTenantContext);

return itemsFetchClient.find(holdingsRecordIds, MoveApiUtil::fetchByHoldingsRecordIdCql)
.thenCompose(jsons -> createItems(jsons, targetTenantItemCollection))
.thenApply(items -> items.stream().map(Item::getId).toList())
.thenCompose(itemIds -> deleteCollectionItems(itemIds, sourceTenantItemCollection));
.thenCompose(jsons -> createItems(jsons, notUpdatedEntities, targetTenantItemCollection))
.thenCompose(items -> deleteItems(items, notUpdatedEntities, sourceTenantItemCollection));
} catch (Exception e) {
LOGGER.warn("transferAttachedItems:: Error during transfer attached items for holdings {}, to tenant: {}",
holdingsRecordIds, targetTenantContext.getTenantId(), e);
return CompletableFuture.failedFuture(e);
}
}

private CompletableFuture<List<Item>> createItems(List<JsonObject> jsons, ItemCollection itemCollection) {
private CompletableFuture<List<Item>> createItems(List<JsonObject> jsons, List<NotUpdatedEntity> notUpdatedEntities, ItemCollection itemCollection) {
List<Item> itemRecordsToUpdateOwnership = jsons.stream()
.map(ItemUtil::fromStoredItemRepresentation)
.toList();

List<CompletableFuture<Item>> createFutures = itemRecordsToUpdateOwnership.stream()
.map(itemCollection::add)
.map(item ->
itemCollection.add(item)
.exceptionally(e -> {
notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).withErrorMessage(e.getMessage()));
throw new CompletionException(e);
}))
.toList();

return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0]))
Expand All @@ -179,7 +197,7 @@ private CompletableFuture<List<Item>> createItems(List<JsonObject> jsons, ItemCo
.toList());
}

private CompletableFuture<List<HoldingsRecord>> createHoldings(List<JsonObject> jsons, String instanceId,
private CompletableFuture<List<HoldingsRecord>> createHoldings(List<JsonObject> jsons, List<NotUpdatedEntity> notUpdatedEntities, String instanceId,
HoldingsRecordCollection holdingsRecordCollection) {
List<HoldingsRecord> holdingsRecordsToUpdateOwnership = jsons.stream()
.peek(MoveApiUtil::removeExtraRedundantFields)
Expand All @@ -188,7 +206,12 @@ private CompletableFuture<List<HoldingsRecord>> createHoldings(List<JsonObject>
.toList();

List<CompletableFuture<HoldingsRecord>> createFutures = holdingsRecordsToUpdateOwnership.stream()
.map(holdingsRecordCollection::add)
.map(holdingRecord ->
holdingsRecordCollection.add(holdingRecord)
.exceptionally(e -> {
notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(holdingRecord.getId()).withErrorMessage(e.getMessage()));
throw new CompletionException(e);
}))
.toList();

return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0]))
Expand All @@ -198,11 +221,35 @@ private CompletableFuture<List<HoldingsRecord>> createHoldings(List<JsonObject>
.toList());
}

private CompletableFuture<List<String>> deleteCollectionItems(List<String> collectionItemIds, AsynchronousCollection<?> collection) {
List<CompletableFuture<String>> deleteFutures = collectionItemIds.stream()
.map(collectionItemId -> {
private CompletableFuture<List<String>> deleteHoldings(List<HoldingsRecord> holdingsRecords, List<NotUpdatedEntity> notUpdatedEntities,
HoldingsRecordCollection holdingsRecordCollection) {
List<CompletableFuture<String>> deleteFutures = holdingsRecords.stream()
.map(holdingsRecord -> {
Promise<String> promise = Promise.promise();
holdingsRecordCollection.delete(holdingsRecord.getId(), success -> promise.complete(holdingsRecord.getId()),
failure -> {
notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(holdingsRecord.getId()).withErrorMessage(failure.getReason()));
promise.fail(failure.getReason());
});
return promise.future().toCompletionStage().toCompletableFuture();
}).toList();

return CompletableFuture.allOf(deleteFutures.toArray(new CompletableFuture[0]))
.handle((vVoid, throwable) -> deleteFutures.stream()
.filter(future -> !future.isCompletedExceptionally())
.map(CompletableFuture::join)
.toList());
}

private CompletableFuture<List<String>> deleteItems(List<Item> itemIds, List<NotUpdatedEntity> notUpdatedEntities, ItemCollection itemCollection) {
List<CompletableFuture<String>> deleteFutures = itemIds.stream()
.map(item -> {
Promise<String> promise = Promise.promise();
collection.delete(collectionItemId, success -> promise.complete(collectionItemId), failure -> promise.fail(failure.getReason()));
itemCollection.delete(item.getId(), success -> promise.complete(item.getId()),
failure -> {
notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).withErrorMessage(failure.getReason()));
promise.fail(failure.getReason());
});
return promise.future().toCompletionStage().toCompletableFuture();
}).toList();

Expand All @@ -212,4 +259,16 @@ private CompletableFuture<List<String>> deleteCollectionItems(List<String> colle
.map(CompletableFuture::join)
.toList());
}

private void processNotFoundedInstances(List<String> holdingsRecordIds, List<NotUpdatedEntity> notUpdatedEntities, WebContext context, List<JsonObject> jsons) {
List<String> foundedIds = jsons.stream().map(json -> json.getString("id")).toList();
List<String> notFoundedIds = ListUtils.subtract(holdingsRecordIds, foundedIds);
notFoundedIds.forEach(id ->
notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(id).withErrorMessage(String.format(INSTANCE_NOT_FOUND, id, context.getTenantId()))));
}

private List<HoldingsRecord> getHoldingsToDelete(List<NotUpdatedEntity> notUpdatedEntities, List<HoldingsRecord> createdHoldings) {
List<String> notUpdatedHoldingsIds = notUpdatedEntities.stream().map(NotUpdatedEntity::getEntityId).toList();
return createdHoldings.stream().filter(holdingsRecord -> !notUpdatedHoldingsIds.contains(holdingsRecord.getId())).toList();
}
}
6 changes: 6 additions & 0 deletions src/main/java/org/folio/inventory/support/MoveApiUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import org.apache.commons.collections4.ListUtils;
import org.folio.NotUpdatedEntity;
import org.folio.UpdateOwnershipResponse;
import org.folio.inventory.common.WebContext;
import org.folio.inventory.storage.external.CollectionResourceClient;
import org.folio.inventory.storage.external.CqlQuery;
Expand Down Expand Up @@ -101,4 +103,8 @@ public static void respond(RoutingContext routingContext, List<String> itemIdsTo
}
}

public static void respond(RoutingContext routingContext, List<NotUpdatedEntity> notUpdatedEntities) {
HttpServerResponse response = routingContext.response();
success(response, JsonObject.mapFrom(new UpdateOwnershipResponse().withNotUpdatedEntities(notUpdatedEntities)));
}
}
Loading

0 comments on commit b5765ae

Please sign in to comment.