From 208a926603b090d55466706b0b88a304bd049931 Mon Sep 17 00:00:00 2001 From: RomanChernetskyi <111740564+RomanChernetskyi@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:22:31 +0200 Subject: [PATCH] [MODINV-952] Not match on central tenant by POL or VRN during consortiua process (#674) --- .../matching/AbstractMatchEventHandler.java | 17 ++- .../preloaders/AbstractPreloader.java | 17 +-- .../matching/util/EventHandlingUtil.java | 18 +++ .../MatchInstanceEventHandlerUnitTest.java | 108 +++++++++++++++++- 4 files changed, 142 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/matching/AbstractMatchEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/matching/AbstractMatchEventHandler.java index ed68863b9..84a16c41a 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/matching/AbstractMatchEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/matching/AbstractMatchEventHandler.java @@ -11,6 +11,7 @@ import org.folio.inventory.consortium.entities.ConsortiumConfiguration; import org.folio.inventory.consortium.services.ConsortiumService; import org.folio.inventory.dataimport.cache.MappingMetadataCache; +import org.folio.inventory.dataimport.handlers.matching.preloaders.PreloadingFields; import org.folio.inventory.dataimport.handlers.matching.util.MatchingParametersRelations; import org.folio.processing.events.services.handler.EventHandler; import org.folio.processing.exceptions.EventProcessingException; @@ -18,10 +19,12 @@ import org.folio.processing.matching.MatchingManager; import org.folio.rest.jaxrs.model.EntityType; import org.folio.MappingMetadataDto; +import org.folio.rest.jaxrs.model.MatchExpression; import java.util.concurrent.CompletableFuture; import static org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil.constructContext; +import static org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil.extractMatchProfile; import static org.folio.inventory.dataimport.util.LoggerUtil.logParametersEventHandler; import static org.folio.rest.jaxrs.model.ProfileSnapshotWrapper.ContentType.MATCH_PROFILE; @@ -57,6 +60,7 @@ public CompletableFuture handle(DataImportEventPayload d .orElse(CompletableFuture.failedFuture(new EventProcessingException(MAPPING_METADATA_NOT_FOUND_MSG)))) .whenComplete((matched, throwable) -> { if (throwable != null) { + LOGGER.warn("handle:: Error during matching", throwable); future.completeExceptionally(throwable); } else { if (Boolean.TRUE.equals(matched)) { @@ -90,7 +94,8 @@ private CompletableFuture matchCentralTenantIfNeeded(DataImportEventPay return consortiumService.getConsortiumConfiguration(context) .toCompletionStage().toCompletableFuture() .thenCompose(consortiumConfiguration -> { - if (consortiumConfiguration.isPresent() && !consortiumConfiguration.get().getCentralTenantId().equals(context.getTenantId())) { + if (consortiumConfiguration.isPresent() && !consortiumConfiguration.get().getCentralTenantId().equals(context.getTenantId()) + && !isMatchByPolOrVrn(dataImportEventPayload)) { LOGGER.debug("matchCentralTenantIfNeeded:: Start matching on central tenant with id: {}", consortiumConfiguration.get().getCentralTenantId()); String localMatchedInstance = dataImportEventPayload.getContext().get(getEntityType().value()); preparePayloadBeforeConsortiumProcessing(dataImportEventPayload, consortiumConfiguration.get(), mappingMetadataDto, matchingParametersRelations); @@ -99,7 +104,7 @@ private CompletableFuture matchCentralTenantIfNeeded(DataImportEventPay dataImportEventPayload.setTenant(context.getTenantId()); if (isMatchedConsortium && isMatchedLocal && !isShadowEntity(localMatchedInstance, dataImportEventPayload.getContext().get(getEntityType().value()))) { LOGGER.warn("matchCentralTenantIfNeeded:: Found multiple results during matching on local tenant: {} and central tenant: {} ", - consortiumConfiguration.get().getCentralTenantId(), context.getTenantId()); + context.getTenantId(), consortiumConfiguration.get().getCentralTenantId()); return CompletableFuture.failedFuture(new MatchingException(String.format(FOUND_MULTIPLE_ENTITIES, context.getTenantId(), consortiumConfiguration.get().getCentralTenantId()))); } if (StringUtils.isEmpty(dataImportEventPayload.getContext().get(getEntityType().value()))) { @@ -116,6 +121,14 @@ private CompletableFuture matchCentralTenantIfNeeded(DataImportEventPay }); } + private boolean isMatchByPolOrVrn(DataImportEventPayload dataImportEventPayload) { + MatchProfile matchProfile = extractMatchProfile(dataImportEventPayload); + MatchExpression matchExpression = matchProfile.getMatchDetails().get(0).getExistingMatchExpression(); + return matchExpression.getFields().stream() + .anyMatch(field -> field.getValue().endsWith("." + PreloadingFields.POL.getExistingMatchField()) + || field.getValue().endsWith("." + PreloadingFields.VRN.getExistingMatchField())); + } + private void preparePayloadBeforeConsortiumProcessing(DataImportEventPayload dataImportEventPayload, ConsortiumConfiguration consortiumConfiguration, MappingMetadataDto mappingMetadataDto, MatchingParametersRelations matchingParametersRelations) { dataImportEventPayload.setTenant(consortiumConfiguration.getCentralTenantId()); diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/matching/preloaders/AbstractPreloader.java b/src/main/java/org/folio/inventory/dataimport/handlers/matching/preloaders/AbstractPreloader.java index 54159c44a..d4301b64b 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/matching/preloaders/AbstractPreloader.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/matching/preloaders/AbstractPreloader.java @@ -23,6 +23,8 @@ import org.folio.rest.jaxrs.model.Field; import org.folio.rest.jaxrs.model.MatchExpression; +import static org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil.extractMatchProfile; + /** * Preloader intended to run some logic to modify existing loading query before passing it to Loader * It check whether match expression contains any of defined fields that imply preloading @@ -58,21 +60,6 @@ public CompletableFuture preload(LoadQuery query, DataImportEventPayl }); } - /** - * Extracts match profile from event payload - * Additional json encoding is needed to return a copy of object not to modify eventPayload - * @return MatchProfile object deep copy - * */ - private MatchProfile extractMatchProfile(DataImportEventPayload dataImportEventPayload) { - if (dataImportEventPayload.getCurrentNode().getContent() instanceof Map) { - return (new JsonObject((Map)dataImportEventPayload.getCurrentNode().getContent())) - .mapTo(MatchProfile.class); - } - - return new JsonObject(Json.encode(dataImportEventPayload.getCurrentNode().getContent())) - .mapTo(MatchProfile.class); - } - /** * Reads incoming record match values from event payload * @return record field values according to match details diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/matching/util/EventHandlingUtil.java b/src/main/java/org/folio/inventory/dataimport/handlers/matching/util/EventHandlingUtil.java index a38c2d6e1..420087af1 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/matching/util/EventHandlingUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/matching/util/EventHandlingUtil.java @@ -2,9 +2,12 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import io.vertx.core.json.Json; import org.apache.commons.lang3.StringUtils; import org.folio.DataImportEventPayload; +import org.folio.MatchProfile; import org.folio.inventory.common.Context; import org.folio.inventory.support.JsonHelper; @@ -59,6 +62,21 @@ public static String getTenant(DataImportEventPayload payload) { return payload.getTenant(); } + /** + * Extracts match profile from event payload + * Additional json encoding is needed to return a copy of object not to modify eventPayload + * @return MatchProfile object deep copy + * */ + public static MatchProfile extractMatchProfile(DataImportEventPayload dataImportEventPayload) { + if (dataImportEventPayload.getCurrentNode().getContent() instanceof Map) { + return (new JsonObject((Map)dataImportEventPayload.getCurrentNode().getContent())) + .mapTo(MatchProfile.class); + } + + return new JsonObject(Json.encode(dataImportEventPayload.getCurrentNode().getContent())) + .mapTo(MatchProfile.class); + } + private static boolean isExistsRequiredProperty(JsonObject representation, String propertyName, String nestedPropertyName) { String propertyValue = StringUtils.isEmpty(nestedPropertyName) ? JsonHelper.getString(representation, propertyName) diff --git a/src/test/java/org/folio/inventory/eventhandlers/MatchInstanceEventHandlerUnitTest.java b/src/test/java/org/folio/inventory/eventhandlers/MatchInstanceEventHandlerUnitTest.java index 63c811989..6026b60cd 100644 --- a/src/test/java/org/folio/inventory/eventhandlers/MatchInstanceEventHandlerUnitTest.java +++ b/src/test/java/org/folio/inventory/eventhandlers/MatchInstanceEventHandlerUnitTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_MATCHED; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_NOT_MATCHED; @@ -47,6 +48,7 @@ import io.vertx.ext.unit.junit.VertxUnitRunner; import org.folio.inventory.consortium.entities.ConsortiumConfiguration; import org.folio.inventory.consortium.services.ConsortiumService; +import org.folio.inventory.dataimport.handlers.matching.preloaders.PreloadingFields; import org.folio.processing.exceptions.MatchingException; import org.junit.Before; import org.junit.Test; @@ -355,6 +357,106 @@ public void shouldNotMatchOnLocalAndMatchOnCentralTenant(TestContext testContext }); } + @Test + public void shouldNotTryToMatchOnCentralTenantIfMatchingByPol(TestContext testContext) throws UnsupportedEncodingException { + Async async = testContext.async(); + + String centralTenantId = "consortium"; + String consortiumId = "consortiumId"; + + Instance instance = new Instance(UUID.randomUUID().toString(), "5", INSTANCE_HRID, "MARC", "Wonderful", "12334"); + + InstanceCollection instanceCollectionCentralTenant = Mockito.mock(InstanceCollection.class); + when(storage.getInstanceCollection(Mockito.argThat(context -> context.getTenantId().equals(centralTenantId)))).thenReturn(instanceCollectionCentralTenant); + + doAnswer(ans -> { + Consumer>> callback = ans.getArgument(2); + Success> result = + new Success<>(new MultipleRecords<>(singletonList(instance), 1)); + callback.accept(result); + return null; + }).when(instanceCollectionCentralTenant) + .findByCql(eq(format("%s == \"%s\"", PreloadingFields.POL.getExistingMatchField(), INSTANCE_HRID)), any(PagingParameters.class), any(Consumer.class), any(Consumer.class)); + + doAnswer(ans -> { + Consumer>> callback = ans.getArgument(2); + Success> result = + new Success<>(new MultipleRecords<>(singletonList(createInstance()), 1)); + callback.accept(result); + return null; + }).when(instanceCollection) + .findByCql(eq(format("%s == \"%s\"", PreloadingFields.POL.getExistingMatchField(), INSTANCE_HRID)), any(PagingParameters.class), any(Consumer.class), any(Consumer.class)); + + doAnswer(invocationOnMock -> Future.succeededFuture(Optional.of(new ConsortiumConfiguration(centralTenantId, consortiumId)))) + .when(consortiumService).getConsortiumConfiguration(any()); + + DataImportEventPayload eventPayload = createEventPayload("instance." + PreloadingFields.POL.getExistingMatchField()); + + eventHandler.handle(eventPayload).whenComplete((updatedEventPayload, throwable) -> { + testContext.assertNull(throwable); + testContext.assertEquals(1, updatedEventPayload.getEventsChain().size()); + testContext.assertEquals( + updatedEventPayload.getEventsChain(), + singletonList(DI_INCOMING_MARC_BIB_RECORD_PARSED.value()) + ); + testContext.assertEquals(DI_INVENTORY_INSTANCE_MATCHED.value(), updatedEventPayload.getEventType()); + JsonObject matchedInstanceAsJsonObject = new JsonObject(updatedEventPayload.getContext().get(INSTANCE.value())); + testContext.assertEquals(matchedInstanceAsJsonObject.getString("id"), INSTANCE_ID); + verify(storage, times(0)).getInstanceCollection(Mockito.argThat(context -> context.getTenantId().equals(centralTenantId))); + async.complete(); + }); + } + + @Test + public void shouldNotTryToMatchOnCentralTenantIfMatchingByVrn(TestContext testContext) throws UnsupportedEncodingException { + Async async = testContext.async(); + + String centralTenantId = "consortium"; + String consortiumId = "consortiumId"; + + Instance instance = new Instance(UUID.randomUUID().toString(), "5", INSTANCE_HRID, "MARC", "Wonderful", "12334"); + + InstanceCollection instanceCollectionCentralTenant = Mockito.mock(InstanceCollection.class); + when(storage.getInstanceCollection(Mockito.argThat(context -> context.getTenantId().equals(centralTenantId)))).thenReturn(instanceCollectionCentralTenant); + + doAnswer(ans -> { + Consumer>> callback = ans.getArgument(2); + Success> result = + new Success<>(new MultipleRecords<>(singletonList(instance), 1)); + callback.accept(result); + return null; + }).when(instanceCollectionCentralTenant) + .findByCql(eq(format("%s == \"%s\"", PreloadingFields.VRN.getExistingMatchField(), INSTANCE_HRID)), any(PagingParameters.class), any(Consumer.class), any(Consumer.class)); + + doAnswer(ans -> { + Consumer>> callback = ans.getArgument(2); + Success> result = + new Success<>(new MultipleRecords<>(singletonList(createInstance()), 1)); + callback.accept(result); + return null; + }).when(instanceCollection) + .findByCql(eq(format("%s == \"%s\"", PreloadingFields.VRN.getExistingMatchField(), INSTANCE_HRID)), any(PagingParameters.class), any(Consumer.class), any(Consumer.class)); + + doAnswer(invocationOnMock -> Future.succeededFuture(Optional.of(new ConsortiumConfiguration(centralTenantId, consortiumId)))) + .when(consortiumService).getConsortiumConfiguration(any()); + + DataImportEventPayload eventPayload = createEventPayload("instance." + PreloadingFields.VRN.getExistingMatchField()); + + eventHandler.handle(eventPayload).whenComplete((updatedEventPayload, throwable) -> { + testContext.assertNull(throwable); + testContext.assertEquals(1, updatedEventPayload.getEventsChain().size()); + testContext.assertEquals( + updatedEventPayload.getEventsChain(), + singletonList(DI_INCOMING_MARC_BIB_RECORD_PARSED.value()) + ); + testContext.assertEquals(DI_INVENTORY_INSTANCE_MATCHED.value(), updatedEventPayload.getEventType()); + JsonObject matchedInstanceAsJsonObject = new JsonObject(updatedEventPayload.getContext().get(INSTANCE.value())); + testContext.assertEquals(matchedInstanceAsJsonObject.getString("id"), INSTANCE_ID); + verify(storage, times(0)).getInstanceCollection(Mockito.argThat(context -> context.getTenantId().equals(centralTenantId))); + async.complete(); + }); + } + @Test public void shouldNotMatchOnHandleEventPayload(TestContext testContext) throws UnsupportedEncodingException { Async async = testContext.async(); @@ -651,6 +753,10 @@ public void shouldReturnFailedFutureWhenFirstChildProfileIsNotMatchProfileOnHand } private DataImportEventPayload createEventPayload() { + return createEventPayload("instance.hrid"); + } + + private DataImportEventPayload createEventPayload(String matchValue) { return new DataImportEventPayload() .withEventType(DI_INCOMING_MARC_BIB_RECORD_PARSED.value()) .withJobExecutionId(UUID.randomUUID().toString()) @@ -670,7 +776,7 @@ private DataImportEventPayload createEventPayload() { .withExistingMatchExpression(new MatchExpression() .withDataValueType(VALUE_FROM_RECORD) .withFields(singletonList( - new Field().withLabel("field").withValue("instance.hrid")) + new Field().withLabel("field").withValue(matchValue)) )))))); }