Skip to content

Commit

Permalink
Merge branch 'master' into msearch-660
Browse files Browse the repository at this point in the history
  • Loading branch information
mukhiddin-yusuf authored Jun 6, 2024
2 parents f29038b + c711e59 commit 0d324ac
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 27 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* Keep right context in resource-id thread ([MSEARCH-754](https://folio-org.atlassian.net/browse/MSEARCH-754))
* Browse: Duplicate results in exact match with diacritics ([MSEARCH-751](https://folio-org.atlassian.net/browse/MSEARCH-751))
* Classification browse: Fix instances count for Shared Facet ([MSEARCH-761](https://folio-org.atlassian.net/browse/MSEARCH-761))
* Subjects/Contributors browse: Fix instances count for Shared Facet ([MSEARCH-782](https://folio-org.atlassian.net/browse/MSEARCH-782))

### Tech Dept
* Re-Index: delete all records from consortium_instance on full re-index ([MSEARCH-744](https://folio-org.atlassian.net/browse/MSEARCH-744))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.folio.search.integration;

import static com.github.jknack.handlebars.internal.lang3.StringUtils.startsWith;
import static java.util.Collections.emptyList;
import static org.apache.commons.codec.digest.DigestUtils.sha1Hex;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
Expand All @@ -13,12 +14,14 @@
import static org.folio.search.utils.SearchConverterUtils.getOldAsMap;
import static org.folio.search.utils.SearchConverterUtils.getResourceEventId;
import static org.folio.search.utils.SearchConverterUtils.getResourceSource;
import static org.folio.search.utils.SearchConverterUtils.isUpdateEventForResourceSharing;
import static org.folio.search.utils.SearchUtils.INSTANCE_CONTRIBUTORS_FIELD_NAME;
import static org.folio.search.utils.SearchUtils.INSTANCE_SUBJECT_RESOURCE;
import static org.folio.search.utils.SearchUtils.SOURCE_CONSORTIUM_PREFIX;

import com.fasterxml.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -66,7 +69,6 @@ private void prepareAndSendEvents(List<ResourceEvent> resourceEvents,
if (isNotEmpty(resourceEvents)) {
resourceEvents.stream()
.filter(Objects::nonNull)
.filter(instance -> !StringUtils.startsWith(getResourceSource(instance), SOURCE_CONSORTIUM_PREFIX))
.map(toEventsFunc)
.flatMap(List::stream)
.forEach(kafkaTemplate::send);
Expand All @@ -75,8 +77,20 @@ private void prepareAndSendEvents(List<ResourceEvent> resourceEvents,

private List<ProducerRecord<String, ResourceEvent>> getSubjectsEvents(ResourceEvent event) {
var tenantId = event.getTenant();
var oldSubjects = extractSubjects(getOldAsMap(event), tenantId);
var newSubjects = extractSubjects(getNewAsMap(event), tenantId);
var shared = isSharedResource(tenantId);
var oldSubjects = extractSubjects(getOldAsMap(event), shared);
var newSubjects = extractSubjects(getNewAsMap(event), shared);

if (isUpdateEventForResourceSharing(event)) {
if (Boolean.TRUE.equals(shared)) {
log.warn("Update event for instance sharing is supposed to be for member tenant,"
+ " but received for central tenant: {}, eventId: {}", tenantId, event.getId());
}
return prepareSubjectsEvents(oldSubjects, List.of(), tenantId, DELETE);
} else if (startsWith(getResourceSource(event), SOURCE_CONSORTIUM_PREFIX)) {
return emptyList();
}

List<ProducerRecord<String, ResourceEvent>> producerRecords = new ArrayList<>();
producerRecords.addAll(prepareSubjectsEvents(newSubjects, oldSubjects, tenantId, CREATE));
producerRecords.addAll(prepareSubjectsEvents(oldSubjects, newSubjects, tenantId, DELETE));
Expand All @@ -85,28 +99,22 @@ private List<ProducerRecord<String, ResourceEvent>> getSubjectsEvents(ResourceEv
return producerRecords;
}

private List<SubjectResourceEvent> extractSubjects(Map<String, Object> objectMap, String tenantId) {
private List<SubjectResourceEvent> extractSubjects(Map<String, Object> objectMap, boolean shared) {
var subjectsObject = getObject(objectMap, SUBJECTS_FIELD, emptyList());
var subjectResourceEvents = jsonConverter.convert(subjectsObject, TYPE_REFERENCE_SUBJECT);
subjectResourceEvents.forEach(
subjectResourceEvent -> {
subjectResourceEvent.setInstanceId(getResourceEventId(objectMap));
subjectResourceEvent.setValue(StringUtils.trim(subjectResourceEvent.getValue()));
subjectResourceEvent.setShared(isSharedResource(objectMap, tenantId));
subjectResourceEvent.setShared(shared);
});
subjectResourceEvents.removeIf(subjectResourceEvent -> StringUtils.isBlank(subjectResourceEvent.getInstanceId()));
return subjectResourceEvents;
}

private boolean isSharedResource(Map<String, Object> objectMap, String tenantId) {
private boolean isSharedResource(String tenantId) {
var centralTenant = consortiumTenantService.getCentralTenant(tenantId);
if (centralTenant.isEmpty()) {
return false;
}

var resourceSource = getResourceSource(objectMap);
return StringUtils.startsWith(resourceSource, SOURCE_CONSORTIUM_PREFIX)
|| centralTenant.get().equals(tenantId);
return centralTenant.isPresent() && centralTenant.get().equals(tenantId);
}

private List<ProducerRecord<String, ResourceEvent>> prepareSubjectsEvents(List<SubjectResourceEvent> subjects,
Expand Down Expand Up @@ -134,11 +142,24 @@ private ResourceEvent convertToSubjectEvent(SubjectResourceEvent subject, String
private List<ProducerRecord<String, ResourceEvent>> getContributorEvents(ResourceEvent event) {
var tenantId = event.getTenant();
var instanceId = getResourceEventId(event);
var shared = isSharedResource(tenantId);

if (StringUtils.isBlank(instanceId)) {
return emptyList();
}
var oldContributors = getContributorEvents(getOldAsMap(event), instanceId, tenantId);
var newContributors = getContributorEvents(getNewAsMap(event), instanceId, tenantId);

var oldContributors = getContributorEvents(getOldAsMap(event), instanceId, shared);
var newContributors = getContributorEvents(getNewAsMap(event), instanceId, shared);

if (isUpdateEventForResourceSharing(event)) {
if (Boolean.TRUE.equals(shared)) {
log.warn("Update event for instance sharing is supposed to be for member tenant,"
+ " but received for central: {}", tenantId);
}
return prepareContributorEvents(new HashSet<>(oldContributors), DELETE, tenantId);
} else if (startsWith(getResourceSource(event), SOURCE_CONSORTIUM_PREFIX)) {
return emptyList();
}

List<ProducerRecord<String, ResourceEvent>> producerRecords = new ArrayList<>();
producerRecords.addAll(prepareContributorEvents(subtract(newContributors, oldContributors), CREATE, tenantId));
Expand All @@ -149,8 +170,7 @@ private List<ProducerRecord<String, ResourceEvent>> getContributorEvents(Resourc
}

private List<ContributorResourceEvent> getContributorEvents(Map<String, Object> objectMap, String instanceId,
String tenantId) {
var shared = isSharedResource(objectMap, tenantId);
boolean shared) {
return extractContributors(objectMap).stream()
.map(contributor -> toContributorEvent(contributor, instanceId, shared))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.apache.commons.collections4.MapUtils.getObject;
import static org.apache.commons.lang3.StringUtils.removeStart;
import static org.apache.commons.lang3.StringUtils.startsWith;
import static org.folio.search.domain.dto.ResourceEventType.UPDATE;
import static org.folio.search.utils.CollectionUtils.subtract;
import static org.folio.search.utils.SearchConverterUtils.getNewAsMap;
import static org.folio.search.utils.SearchConverterUtils.getOldAsMap;
import static org.folio.search.utils.SearchConverterUtils.getResourceEventId;
import static org.folio.search.utils.SearchConverterUtils.getResourceSource;
import static org.folio.search.utils.SearchConverterUtils.isUpdateEventForResourceSharing;
import static org.folio.search.utils.SearchUtils.CLASSIFICATIONS_FIELD;
import static org.folio.search.utils.SearchUtils.CLASSIFICATION_NUMBER_FIELD;
import static org.folio.search.utils.SearchUtils.CLASSIFICATION_TYPE_FIELD;
Expand Down Expand Up @@ -62,7 +62,7 @@ public List<ResourceEvent> preProcess(ResourceEvent event) {

List<ResourceEvent> events;

if (isUpdateForInstanceSharing(event)) {
if (isUpdateEventForResourceSharing(event)) {
events = prepareClassificationEventsOnInstanceSharing(event);
} else if (startsWith(getResourceSource(event), SOURCE_CONSORTIUM_PREFIX)) {
log.info("preProcess::Finished instance event pre-processing. No additional events created for shadow instance.");
Expand All @@ -78,13 +78,6 @@ public List<ResourceEvent> preProcess(ResourceEvent event) {
return events;
}

private boolean isUpdateForInstanceSharing(ResourceEvent event) {
var newSource = getResourceSource(getNewAsMap(event));
return event.getType() == UPDATE
&& startsWith(newSource, SOURCE_CONSORTIUM_PREFIX)
&& Objects.equals(getResourceSource(getOldAsMap(event)), removeStart(newSource, SOURCE_CONSORTIUM_PREFIX));
}

private List<ResourceEvent> prepareClassificationEventsOnInstanceSharing(ResourceEvent event) {
if (!featureConfigService.isEnabled(TenantConfiguredFeature.BROWSE_CLASSIFICATIONS)) {
return emptyList();
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/folio/search/utils/SearchConverterUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import static java.util.Collections.emptyMap;
import static org.apache.commons.collections4.MapUtils.getString;
import static org.apache.commons.lang3.StringUtils.removeStart;
import static org.apache.commons.lang3.StringUtils.startsWith;
import static org.folio.search.domain.dto.ResourceEventType.UPDATE;
import static org.folio.search.utils.SearchUtils.ID_FIELD;
import static org.folio.search.utils.SearchUtils.SOURCE_CONSORTIUM_PREFIX;
import static org.folio.search.utils.SearchUtils.SOURCE_FIELD;

import java.util.Arrays;
Expand Down Expand Up @@ -182,6 +186,13 @@ public static void copyEntityFields(Map<String, Object> source, Map<String, Obje
}
}

public static boolean isUpdateEventForResourceSharing(ResourceEvent event) {
var newSource = getResourceSource(getNewAsMap(event));
return event.getType() == UPDATE
&& startsWith(newSource, SOURCE_CONSORTIUM_PREFIX)
&& Objects.equals(getResourceSource(getOldAsMap(event)), removeStart(newSource, SOURCE_CONSORTIUM_PREFIX));
}

@SuppressWarnings("unchecked")
private static Object getFieldValueByPath(String pathValue, Object value) {
if (value instanceof Map) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.folio.search.integration;

import static java.util.Collections.singletonList;
import static org.apache.commons.codec.digest.DigestUtils.sha1Hex;
import static org.apache.logging.log4j.util.Strings.toRootLowerCase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static org.folio.search.domain.dto.ResourceEventType.CREATE;
import static org.folio.search.domain.dto.ResourceEventType.DELETE;
import static org.folio.search.domain.dto.ResourceEventType.UPDATE;
import static org.folio.search.utils.SearchUtils.ID_FIELD;
import static org.folio.search.utils.SearchUtils.INSTANCE_RESOURCE;
Expand All @@ -13,12 +18,14 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.folio.search.domain.dto.ResourceEvent;
import org.folio.search.model.event.ContributorResourceEvent;
import org.folio.search.service.consortium.ConsortiumTenantService;
import org.folio.search.utils.JsonConverter;
import org.folio.spring.testing.type.UnitTest;
Expand All @@ -27,6 +34,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
Expand All @@ -38,6 +46,8 @@
@ExtendWith(MockitoExtension.class)
class KafkaMessageProducerTest {

private static final ArgumentCaptor<ProducerRecord> CAPTOR = ArgumentCaptor.forClass(ProducerRecord.class);

@InjectMocks
private KafkaMessageProducer producer;
@Spy
Expand Down Expand Up @@ -103,6 +113,34 @@ void shouldSendNoSubjectEvents_whenInstanceIdIsBlank(String instanceId) {
verify(kafkaTemplate, never()).send(ArgumentMatchers.<ProducerRecord<String, ResourceEvent>>any());
}

@Test
void shouldSendDeleteOldSubjectEventOfMemberTenant_whenInstanceUpdatedWithSharing() {
var instanceId = randomId();
var name = "Medicine";
var oldContributorObject = subjectObject(name);
var newContributorObject = subjectObject(name);
var resourceEvent = resourceEvent(instanceId, INSTANCE_RESOURCE, UPDATE,
instanceObjectWithSubjects(instanceId, newContributorObject, SOURCE_CONSORTIUM_PREFIX + "FOLIO"),
instanceObjectWithSubjects(instanceId, oldContributorObject, "FOLIO")
);
final var expectedOld = mapOf(
"authorityId", null,
"id", sha1Hex(toRootLowerCase(name + "|null")),
"value", name,
"instanceId", instanceId,
"shared", false
);

producer.prepareAndSendSubjectEvents(singletonList(resourceEvent));

verify(kafkaTemplate).send(CAPTOR.capture());
var record = (ResourceEvent) CAPTOR.getValue().value();
assertThat(List.of(record))
.extracting(ResourceEvent::getType, ResourceEvent::getNew)
.containsExactlyInAnyOrder(tuple(DELETE, null));
assertThat(record.getOld()).isEqualTo(expectedOld);
}

@Test
void shouldSendTwoContributorEvents_whenContributorChanged() {
var instanceId = randomId();
Expand All @@ -113,11 +151,55 @@ void shouldSendTwoContributorEvents_whenContributorChanged() {
instanceObjectWithContributors(instanceId, newContributorObject),
instanceObjectWithContributors(instanceId, oldContributorObject)
);

producer.prepareAndSendContributorEvents(singletonList(resourceEvent));

verify(kafkaTemplate, times(2)).send(ArgumentMatchers.<ProducerRecord<String, ResourceEvent>>any());
}

@Test
void shouldSendDeleteOldContributorEventOfMemberTenant_whenInstanceUpdatedWithSharing() {
var instanceId = randomId();
var typeId = randomId();
var oldContributorObject = contributorObject(typeId, "Skywalker, Luke");
var newContributorObject = contributorObject(typeId, "Skywalker, Luke");
var resourceEvent = resourceEvent(instanceId, INSTANCE_RESOURCE, UPDATE,
instanceObjectWithContributors(instanceId, newContributorObject, SOURCE_CONSORTIUM_PREFIX + "FOLIO"),
instanceObjectWithContributors(instanceId, oldContributorObject, "FOLIO")
);
final var expectedOld = ContributorResourceEvent.builder()
.id(sha1Hex(typeId + "|" + toRootLowerCase(oldContributorObject.get("name") + "|null")))
.typeId(typeId)
.nameTypeId(typeId)
.shared(false)
.name(oldContributorObject.get("name"))
.instanceId(instanceId)
.build();

producer.prepareAndSendContributorEvents(singletonList(resourceEvent));

verify(kafkaTemplate).send(CAPTOR.capture());
var record = (ResourceEvent) CAPTOR.getValue().value();
assertThat(List.of(record))
.extracting(ResourceEvent::getType, ResourceEvent::getNew)
.containsExactlyInAnyOrder(tuple(DELETE, null));
assertThat((ContributorResourceEvent) record.getOld()).isEqualTo(expectedOld);
}

@Test
void shouldSendNoContributorEvents_whenInstanceWithConsortiumSourceCreated() {
var instanceId = randomId();
var typeId = randomId();
var newContributorObject = contributorObject(typeId, "Skywalker, Luke");
var resourceEvent = resourceEvent(instanceId, INSTANCE_RESOURCE,
instanceObjectWithContributors(instanceId, newContributorObject, SOURCE_CONSORTIUM_PREFIX + "FOLIO")
);

producer.prepareAndSendContributorEvents(singletonList(resourceEvent));

verifyNoInteractions(kafkaTemplate);
}

@Test
void shouldSendNoContributorEvents_whenContributorNotChanged() {
var instanceId = randomId();
Expand Down Expand Up @@ -175,14 +257,26 @@ private Map<String, String> instanceObjectWithContributors(String id, Map<String
return mapOf(ID_FIELD, id, "contributors", List.of(contributorObject));
}

@NotNull
private Map<String, String> instanceObjectWithContributors(String id,
Map<String, String> contributorObject,
String source) {
return mapOf(ID_FIELD, id, "contributors", List.of(contributorObject), SOURCE_FIELD, source);
}

@NotNull
private Map<String, String> instanceObjectWithSubjects(String id, Map<String, String> subjectObject) {
return mapOf(ID_FIELD, id, "subjects", List.of(subjectObject), SOURCE_FIELD, "FOLIO");
}

@NotNull
private Map<String, String> instanceObjectWithSubjects(String id, Map<String, String> subjectObject, String source) {
return mapOf(ID_FIELD, id, "subjects", List.of(subjectObject), SOURCE_FIELD, source);
}

@NotNull
private Map<String, String> contributorObject(String typeId, String name) {
return mapOf("contributorNameTypeId", typeId, "name", name);
return mapOf("contributorNameTypeId", typeId, "name", name, "contributorTypeId", typeId);
}

@NotNull
Expand Down

0 comments on commit 0d324ac

Please sign in to comment.