Skip to content

Commit

Permalink
Merge branch 'feature/call-number-browse-v2' into MSEARCH-862
Browse files Browse the repository at this point in the history
  • Loading branch information
psmagin authored Nov 29, 2024
2 parents 4db9a14 + 21cecb8 commit 075cc42
Show file tree
Hide file tree
Showing 26 changed files with 381 additions and 119 deletions.
3 changes: 2 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,8 @@
],
"modulePermissions": [
"user-tenants.collection.get",
"inventory-storage.classification-types.collection.get"
"inventory-storage.classification-types.collection.get",
"inventory-storage.call-number-types.collection.get"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,28 @@ public void handleAuthorityEvents(List<ConsumerRecord<String, ResourceEvent>> co
}

@KafkaListener(
id = KafkaConstants.CLASSIFICATION_TYPE_LISTENER_ID,
id = KafkaConstants.BROWSE_CONFIG_DATA_LISTENER_ID,
containerFactory = "resourceListenerContainerFactory",
groupId = "#{folioKafkaProperties.listener['classification-type'].groupId}",
concurrency = "#{folioKafkaProperties.listener['classification-type'].concurrency}",
topicPattern = "#{folioKafkaProperties.listener['classification-type'].topicPattern}")
groupId = "#{folioKafkaProperties.listener['browse-config-data'].groupId}",
concurrency = "#{folioKafkaProperties.listener['browse-config-data'].concurrency}",
topicPattern = "#{folioKafkaProperties.listener['browse-config-data'].topicPattern}")
@CacheEvict(cacheNames = REFERENCE_DATA_CACHE, allEntries = true)
public void handleClassificationTypeEvents(List<ConsumerRecord<String, ResourceEvent>> consumerRecords) {
log.info("Processing classification-type events from Kafka [number of events: {}]", consumerRecords.size());
public void handleBrowseConfigDataEvents(List<ConsumerRecord<String, ResourceEvent>> consumerRecords) {
log.info("Processing browse config data events from Kafka [number of events: {}]", consumerRecords.size());
var batch = consumerRecords.stream()
.map(ConsumerRecord::value)
.filter(resourceEvent -> resourceEvent.getType() == DELETE).toList();
.filter(resourceEvent -> resourceEvent.getType() == DELETE)
.toList();

var batchByTenant = batch.stream().collect(Collectors.groupingBy(ResourceEvent::getTenant));

batchByTenant.forEach((tenant, resourceEvents) -> executionService.executeSystemUserScoped(tenant, () -> {
folioMessageBatchProcessor.consumeBatchWithFallback(batch, KAFKA_RETRY_TEMPLATE_NAME,
resourceEvent -> configSynchronizationService.sync(resourceEvent, ResourceType.CLASSIFICATION_TYPE),
resourceEvent -> {
var eventsByResource = resourceEvent.stream().collect(Collectors.groupingBy(ResourceEvent::getResourceName));
eventsByResource.forEach((resourceName, events) ->
configSynchronizationService.sync(resourceEvent, ResourceType.byName(resourceName)));
},
KafkaMessageListener::logFailedEvent);
return null;
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class ResourceEventBatchInterceptor implements BatchInterceptor<String, R
Map.entry("search.instance-contributor", ResourceType.INSTANCE_CONTRIBUTOR),
Map.entry("search.instance-subject", ResourceType.INSTANCE_SUBJECT),
Map.entry("inventory.classification-type", ResourceType.CLASSIFICATION_TYPE),
Map.entry("inventory.call-number-type", ResourceType.CALL_NUMBER_TYPE),
Map.entry("inventory.location", ResourceType.LOCATION),
Map.entry("inventory.campus", ResourceType.CAMPUS),
Map.entry("inventory.institution", ResourceType.INSTITUTION),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum ResourceType {
BOUND_WITH("bound_with"),
CAMPUS("campus"),
CLASSIFICATION_TYPE("classification-type"),
CALL_NUMBER_TYPE("call-number-type"),
HOLDINGS("holdings"),
INSTANCE("instance"),
INSTANCE_CONTRIBUTOR("contributor"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected String getValueForBrowsing(ClassificationNumberBrowseItem browseItem)
@Override
protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseContext ctx) {
log.debug("getAnchorSearchQuery:: by [request: {}]", req);
var config = configService.getConfig(BrowseType.INSTANCE_CLASSIFICATION, req.getBrowseOptionType());
var config = configService.getConfig(BrowseType.CLASSIFICATION, req.getBrowseOptionType());

var browseField = getBrowseField(config);
var termQueryBuilder = getQuery(ctx, config, termQuery(req.getTargetField(), ctx.getAnchor()));
Expand All @@ -66,7 +66,7 @@ protected SearchSourceBuilder getAnchorSearchQuery(BrowseRequest req, BrowseCont
@Override
protected SearchSourceBuilder getSearchQuery(BrowseRequest req, BrowseContext ctx, boolean isBrowsingForward) {
log.debug("getSearchQuery:: by [request: {}, isBrowsingForward: {}]", req, isBrowsingForward);
var config = configService.getConfig(BrowseType.INSTANCE_CLASSIFICATION, req.getBrowseOptionType());
var config = configService.getConfig(BrowseType.CLASSIFICATION, req.getBrowseOptionType());

var browseField = getBrowseField(config);
var normalizedAnchor = ShelvingOrderCalculationHelper.calculate(ctx.getAnchor(), config.getShelvingAlgorithm());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.folio.search.service.config;

import static org.folio.search.client.InventoryReferenceDataClient.ReferenceDataType.CALL_NUMBER_TYPES;
import static org.folio.search.client.InventoryReferenceDataClient.ReferenceDataType.CLASSIFICATION_TYPES;
import static org.folio.search.configuration.SearchCacheNames.BROWSE_CONFIG_CACHE;

Expand Down Expand Up @@ -34,7 +35,7 @@
public class BrowseConfigService {

private static final String ID_VALIDATION_MSG = "Body doesn't match path parameter: %s";
private static final String TYPE_IDS_VALIDATION_MSG = "Classification type IDs don't exist";
private static final String TYPE_IDS_VALIDATION_MSG = "%s type IDs don't exist";

private final ReferenceDataService referenceDataService;
private final BrowseConfigEntityRepository repository;
Expand Down Expand Up @@ -63,7 +64,7 @@ public BrowseConfig getConfig(@NonNull BrowseType type, @NonNull BrowseOptionTyp
public void upsertConfig(@NonNull BrowseType type,
@NonNull BrowseOptionType optionType,
@NonNull BrowseConfig config) {
validateConfig(optionType, config);
validateConfig(type, optionType, config);

log.debug("Update browse configuration option [browseType: {}, browseOptionType: {}, newValue: {}]",
type.getValue(), optionType.getValue(), config);
Expand All @@ -87,17 +88,18 @@ public void deleteTypeIdsFromConfigs(@NonNull BrowseType type, @NonNull List<Str
repository.saveAll(configs);
}

private void validateConfig(BrowseOptionType optionType, BrowseConfig config) {
private void validateConfig(BrowseType type, BrowseOptionType optionType, BrowseConfig config) {
validateOptionType(optionType, config);
validateTypeIds(config);
validateTypeIds(type, config);
}

private void validateTypeIds(BrowseConfig config) {
private void validateTypeIds(BrowseType type, BrowseConfig config) {
var ids = CollectionUtils.toStreamSafe(config.getTypeIds()).map(UUID::toString).collect(Collectors.toSet());
var existedIds = referenceDataService.fetchReferenceData(CLASSIFICATION_TYPES, CqlQueryParam.ID, ids);
var referenceDataType = BrowseType.CLASSIFICATION.equals(type) ? CLASSIFICATION_TYPES : CALL_NUMBER_TYPES;
var existedIds = referenceDataService.fetchReferenceData(referenceDataType, CqlQueryParam.ID, ids);
var difference = SetUtils.difference(ids, existedIds);
if (!difference.isEmpty()) {
throw new RequestValidationException(TYPE_IDS_VALIDATION_MSG, "typeIds", difference.toString());
throw new RequestValidationException(TYPE_IDS_VALIDATION_MSG.formatted(type), "typeIds", difference.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package org.folio.search.service.config;

import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.ResourceEvent;
import org.folio.search.model.types.ResourceType;
import org.folio.search.service.consortium.BrowseConfigServiceDecorator;
import org.springframework.stereotype.Service;

@Log4j2
@Service
@RequiredArgsConstructor
public class ConfigSynchronizationService {
Expand All @@ -18,10 +21,16 @@ public void sync(List<ResourceEvent> resourceEvent, ResourceType resourceType) {
if (resourceEvent == null || resourceEvent.isEmpty()) {
return;
}
if (resourceType == ResourceType.CLASSIFICATION_TYPE) {
var ids = resourceEvent.stream().map(ResourceEvent::getId).toList();
configService.deleteTypeIdsFromConfigs(BrowseType.INSTANCE_CLASSIFICATION, ids);
}

Optional.ofNullable(resourceType)
.map(resource -> switch (resourceType) {
case CLASSIFICATION_TYPE -> BrowseType.CLASSIFICATION;
case CALL_NUMBER_TYPE -> BrowseType.CALL_NUMBER;
default -> null; })
.ifPresentOrElse(browseType -> {
var ids = resourceEvent.stream().map(ResourceEvent::getId).toList();
configService.deleteTypeIdsFromConfigs(browseType, ids);
}, () -> log.warn("sync:: not supported resource type: [{}]", resourceType));
}

}
2 changes: 1 addition & 1 deletion src/main/java/org/folio/search/utils/KafkaConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class KafkaConstants {

public static final String AUTHORITY_LISTENER_ID = "mod-search-authorities-listener";
public static final String EVENT_LISTENER_ID = "mod-search-events-listener";
public static final String CLASSIFICATION_TYPE_LISTENER_ID = "mod-search-classification-type-listener";
public static final String BROWSE_CONFIG_DATA_LISTENER_ID = "mod-search-browse-config-data-listener";
public static final String LOCATION_LISTENER_ID = "mod-search-location-listener";
public static final String LINKED_DATA_LISTENER_ID = "mod-search-linked-data-listener";
public static final String REINDEX_RANGE_INDEX_LISTENER_ID = "mod-search-reindex-index-listener";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import java.util.Locale;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import org.folio.search.cql.SuDocCallNumber;
import org.folio.search.domain.dto.ShelvingOrderAlgorithmType;
import org.marc4j.callnum.DeweyCallNumber;
import org.marc4j.callnum.LCCallNumber;
import org.marc4j.callnum.NlmCallNumber;

@UtilityClass
public class ShelvingOrderCalculationHelper {
Expand All @@ -14,6 +16,8 @@ public static String calculate(@NonNull String input, @NonNull ShelvingOrderAlgo
return switch (algorithmType) {
case LC -> new LCCallNumber(input).getShelfKey().trim();
case DEWEY -> new DeweyCallNumber(input).getShelfKey().trim();
case NLM -> new NlmCallNumber(input).getShelfKey().trim();
case SUDOC -> new SuDocCallNumber(input).getShelfKey().trim();
case DEFAULT -> normalize(input);
};
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ folio:
concurrency: ${KAFKA_AUTHORITIES_CONCURRENCY:1}
topic-pattern: ${KAFKA_AUTHORITIES_CONSUMER_PATTERN:(${folio.environment}\.)(.*\.)authorities\.authority}
group-id: ${folio.environment}-mod-search-authorities-group
classification-type:
concurrency: ${KAFKA_CLASSIFICATION_TYPE_CONCURRENCY:1}
topic-pattern: (${folio.environment}\.)(.*\.)inventory\.classification-type
group-id: ${folio.environment}-mod-search-classification-type-group
browse-config-data:
concurrency: ${KAFKA_BROWSE_CONFIG_DATA_CONCURRENCY:1}
topic-pattern: (${folio.environment}\.)(.*\.)inventory\.(classification-type|call-number-type)
group-id: ${folio.environment}-mod-search-browse-config-data-group
location:
concurrency: ${KAFKA_LOCATION_CONCURRENCY:1}
topic-pattern: (${folio.environment}\.)(.*\.)inventory\.(location|campus|institution|library)
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/changelog/changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
<include file="changes/v4.0/create-reindex-entity-tables.xml" relativeToChangelogFile="true"/>
<include file="changes/v4.0/delete-instance-trigger.xml" relativeToChangelogFile="true"/>
<include file="changes/v4.1/create_call_number_tables.xml" relativeToChangelogFile="true"/>
<include file="changes/v4.1/populate-call-number-browse-config.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">

<changeSet id="MSEARCH-863@@populate_call_number_browse_config" author="viacheslav_kolesnyk">
<preConditions>
<tableExists tableName="browse_config"/>
</preConditions>

<comment>Populate browse_config table with default call number values</comment>

<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="all"/>
<column name="shelving_algorithm" value="default"/>
</insert>
<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="lc"/>
<column name="shelving_algorithm" value="lc"/>
</insert>
<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="dewey"/>
<column name="shelving_algorithm" value="dewey"/>
</insert>
<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="nlm"/>
<column name="shelving_algorithm" value="nlm"/>
</insert>
<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="sudoc"/>
<column name="shelving_algorithm" value="sudoc"/>
</insert>
<insert tableName="browse_config">
<column name="browse_type" value="instance-call-number"/>
<column name="browse_option_type" value="other"/>
<column name="shelving_algorithm" value="default"/>
</insert>
</changeSet>


</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ enum:
- all
- lc
- dewey
- nlm
- sudoc
- other
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
type: string
enum:
- instance-classification
- instance-call-number
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ type: string
enum:
- lc
- dewey
- nlm
- sudoc
- default
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private static void updateLcConfig(List<UUID> typeIds) {
.typeIds(typeIds);

var stub = mockClassificationTypes(okapi.wireMockServer(), typeIds.toArray(new UUID[0]));
doPut(browseConfigPath(BrowseType.INSTANCE_CLASSIFICATION, BrowseOptionType.LC), config);
doPut(browseConfigPath(BrowseType.CLASSIFICATION, BrowseOptionType.LC), config);
okapi.wireMockServer().removeStub(stub);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.folio.search.controller;

import static java.util.UUID.randomUUID;
import static org.folio.search.domain.dto.BrowseType.INSTANCE_CLASSIFICATION;
import static org.folio.search.domain.dto.TenantConfiguredFeature.SEARCH_ALL_FIELDS;
import static org.folio.search.utils.TestConstants.TENANT_ID;
import static org.folio.search.utils.TestUtils.asJsonString;
Expand All @@ -26,6 +25,7 @@
import org.folio.search.domain.dto.BrowseConfig;
import org.folio.search.domain.dto.BrowseConfigCollection;
import org.folio.search.domain.dto.BrowseOptionType;
import org.folio.search.domain.dto.BrowseType;
import org.folio.search.domain.dto.FeatureConfig;
import org.folio.search.domain.dto.FeatureConfigs;
import org.folio.search.domain.dto.LanguageConfigs;
Expand All @@ -40,6 +40,8 @@
import org.folio.spring.testing.type.UnitTest;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
Expand Down Expand Up @@ -277,14 +279,15 @@ void deleteFeatureConfigurationById_positive() throws Exception {
mockMvc.perform(request).andExpect(status().isNoContent());
}

@Test
void getBrowseConfigs_positive() throws Exception {
@ParameterizedTest
@EnumSource(BrowseType.class)
void getBrowseConfigs_positive(BrowseType type) throws Exception {
var config = new BrowseConfig().id(BrowseOptionType.LC).shelvingAlgorithm(ShelvingOrderAlgorithmType.LC)
.typeIds(List.of(randomUUID(), randomUUID()));
when(browseConfigService.getConfigs(INSTANCE_CLASSIFICATION))
when(browseConfigService.getConfigs(type))
.thenReturn(new BrowseConfigCollection().addConfigsItem(config).totalRecords(1));

var request = get(ApiEndpoints.browseConfigPath(INSTANCE_CLASSIFICATION))
var request = get(ApiEndpoints.browseConfigPath(type))
.header(XOkapiHeaders.TENANT, TENANT_ID)
.contentType(APPLICATION_JSON);

Expand All @@ -297,13 +300,14 @@ void getBrowseConfigs_positive() throws Exception {
containsInAnyOrder(config.getTypeIds().stream().map(UUID::toString).toArray())));
}

@Test
void putBrowseConfig_positive() throws Exception {
@ParameterizedTest
@EnumSource(BrowseType.class)
void putBrowseConfig_positive(BrowseType type) throws Exception {
var config = new BrowseConfig().id(BrowseOptionType.LC).shelvingAlgorithm(ShelvingOrderAlgorithmType.LC)
.typeIds(List.of(randomUUID(), randomUUID()));
doNothing().when(browseConfigService).upsertConfig(INSTANCE_CLASSIFICATION, BrowseOptionType.LC, config);
doNothing().when(browseConfigService).upsertConfig(type, BrowseOptionType.LC, config);

var request = put(ApiEndpoints.browseConfigPath(INSTANCE_CLASSIFICATION, BrowseOptionType.LC))
var request = put(ApiEndpoints.browseConfigPath(type, BrowseOptionType.LC))
.header(XOkapiHeaders.TENANT, TENANT_ID)
.contentType(APPLICATION_JSON)
.content(asJsonString(config));
Expand Down
Loading

0 comments on commit 075cc42

Please sign in to comment.