Skip to content

Commit

Permalink
MODLD-385: Create beans for Default and StructureB LCCN normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
pkjacob committed May 28, 2024
1 parent f790b95 commit 730a010
Show file tree
Hide file tree
Showing 18 changed files with 168 additions and 114 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.folio.search.cql;

import lombok.RequiredArgsConstructor;
import org.folio.search.utils.SearchUtils;
import org.folio.search.service.lccn.LccnNormalizer;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class LccnSearchTermProcessor implements SearchTermProcessor {

private final LccnNormalizer lccnNormalizer;

@Override
public String getSearchTerm(String inputTerm) {
return SearchUtils.normalizeLccn(inputTerm);
return lccnNormalizer.apply(inputTerm)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ public void handleBibframeEvents(List<ConsumerRecord<String, ResourceEvent>> con
.map(bibframe -> bibframe.resourceName(BIBFRAME_RESOURCE).id(getResourceEventId(bibframe)))
.toList();

folioMessageBatchProcessor.consumeBatchWithFallback(batch, KAFKA_RETRY_TEMPLATE_NAME,
resourceService::indexResources, KafkaMessageListener::logFailedEvent);
indexResources(batch, resourceService::indexResources);
}

private void indexResources(List<ResourceEvent> batch, Consumer<List<ResourceEvent>> indexConsumer) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.folio.search.service.lccn;

import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

@Service
@Primary
public class DefaultLccnNormalizer implements LccnNormalizer {

@Override
public Optional<String> apply(String lccn) {
if (StringUtils.isBlank(lccn)) {
return Optional.empty();
}

return Optional.of(StringUtils.deleteWhitespace(lccn))
.map(String::toLowerCase);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.folio.search.service.lccn;

import java.util.Optional;
import java.util.function.Function;

public interface LccnNormalizer extends Function<String, Optional<String>> {
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.folio.search.service.setter.bibframe;
package org.folio.search.service.lccn;

import jakarta.validation.constraints.NotNull;
import java.util.Optional;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
* Class responsible for normalizing Structure B LCCN values.
*/
@Log4j2
@Service
public class LccnNormalizer {
public class LccnNormalizerStructureB implements LccnNormalizer {
private static final String NORMALIZED_LCCN_REGEX = "\\d{10}";
private static final char HYPHEN = '-';

Expand All @@ -19,7 +22,7 @@ public class LccnNormalizer {
* @param lccn LCCN to be normalized
* @return Returns the normalized LCCN. If the given LCCN is invalid, returns an empty Optional
*/
public Optional<String> normalizeLccn(@NotNull final String lccn) {
public Optional<String> apply(@NotNull final String lccn) {
var normalizedLccn = lccn;

// Remove white spaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@
import java.util.stream.Collectors;
import org.folio.search.domain.dto.Identifier;
import org.folio.search.integration.ReferenceDataService;
import org.folio.search.utils.SearchUtils;
import org.folio.search.service.lccn.LccnNormalizer;

public abstract class AbstractLccnProcessor<T> extends AbstractIdentifierProcessor<T> {

private static final List<String> LCCN_IDENTIFIER_NAME = List.of("LCCN", "Canceled LCCN");
private final LccnNormalizer lccnNormalizer;

protected AbstractLccnProcessor(ReferenceDataService referenceDataService) {
protected AbstractLccnProcessor(ReferenceDataService referenceDataService, LccnNormalizer lccnNormalizer) {
super(referenceDataService, LCCN_IDENTIFIER_NAME);
this.lccnNormalizer = lccnNormalizer;
}

@Override
public Set<String> getFieldValue(T entity) {
return filterIdentifiersValue(getIdentifiers(entity)).stream()
.map(SearchUtils::normalizeLccn)
.flatMap(s -> lccnNormalizer.apply(s).stream())
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.folio.search.domain.dto.Authority;
import org.folio.search.domain.dto.Identifier;
import org.folio.search.integration.ReferenceDataService;
import org.folio.search.service.lccn.LccnNormalizer;
import org.folio.search.service.setter.AbstractLccnProcessor;
import org.springframework.stereotype.Component;

Expand All @@ -16,9 +17,10 @@ public class LccnAuthorityProcessor extends AbstractLccnProcessor<Authority> {
* Used by dependency injection.
*
* @param referenceDataService {@link ReferenceDataService} bean
* @param lccnNormalizer {@link LccnNormalizer} bean
*/
public LccnAuthorityProcessor(ReferenceDataService referenceDataService) {
super(referenceDataService);
public LccnAuthorityProcessor(ReferenceDataService referenceDataService, LccnNormalizer lccnNormalizer) {
super(referenceDataService, lccnNormalizer);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.Bibframe;
import org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner;
import org.folio.search.service.lccn.LccnNormalizer;
import org.folio.search.service.setter.FieldProcessor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class BibframeLccnProcessor implements FieldProcessor<Bibframe, Set<String>> {

@Qualifier("StructBLccnNormalizer")
private final LccnNormalizer lccnNormalizer;

@Override
Expand All @@ -31,7 +34,7 @@ public Set<String> getFieldValue(Bibframe bibframe) {
.filter(i -> LCCN.equals(i.getType()))
.map(BibframeInstancesInnerIdentifiersInner::getValue)
.filter(Objects::nonNull)
.map(lccnNormalizer::normalizeLccn)
.map(lccnNormalizer)
.flatMap(Optional::stream)
.collect(toCollection(LinkedHashSet::new));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.folio.search.domain.dto.Identifier;
import org.folio.search.domain.dto.Instance;
import org.folio.search.integration.ReferenceDataService;
import org.folio.search.service.lccn.LccnNormalizer;
import org.folio.search.service.setter.AbstractLccnProcessor;
import org.springframework.stereotype.Component;

Expand All @@ -19,9 +20,10 @@ public class LccnInstanceProcessor extends AbstractLccnProcessor<Instance> {
* Used by dependency injection.
*
* @param referenceDataService {@link ReferenceDataService} bean
* @param lccnNormalizer {@link LccnNormalizer} bean
*/
public LccnInstanceProcessor(ReferenceDataService referenceDataService) {
super(referenceDataService);
public LccnInstanceProcessor(ReferenceDataService referenceDataService, LccnNormalizer lccnNormalizer) {
super(referenceDataService, lccnNormalizer);
}

@Override
Expand Down
14 changes: 0 additions & 14 deletions src/main/java/org/folio/search/utils/SearchUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -329,20 +329,6 @@ public static int getNumberOfRequests(Map<String, List<SearchDocumentBody>> requ
.sum();
}

/**
* Normalizes LCCN value.
*
* @param value LCCN value
* @return normalized LCCN value
*/
public static String normalizeLccn(String value) {
if (StringUtils.isBlank(value)) {
return null;
}

return StringUtils.deleteWhitespace(value).toLowerCase();
}

/**
* This method normalize the given string to an alphanumeric string.
* If the input string is null or blank, this method returns null.
Expand Down
3 changes: 0 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ folio:
- name: search.consortium.instance
numPartitions: ${KAFKA_CONSORTIUM_INSTANCE_TOPIC_PARTITIONS:50}
replicationFactor: ${KAFKA_CONSORTIUM_INSTANCE_TOPIC_REPLICATION_FACTOR:}
- name: search.bibframe
numPartitions: ${KAFKA_BIBFRAME_TOPIC_PARTITIONS:3}
replicationFactor: ${KAFKA_BIBFRAME_TOPIC_REPLICATION_FACTOR:}
listener:
events:
concurrency: ${KAFKA_EVENTS_CONCURRENCY:2}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
package org.folio.search.cql;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.util.Optional;
import org.folio.search.service.lccn.LccnNormalizer;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;


@UnitTest
@ExtendWith(MockitoExtension.class)
class LccnSearchTermProcessorTest {
@Mock
private LccnNormalizer normalizer;
@InjectMocks
private LccnSearchTermProcessor lccnSearchTermProcessor;

@Test
void getSearchTerm_positive() {
// given
var searchTerm = " N 123456 ";
var lccnSearchTermProcessor = new LccnSearchTermProcessor();
var normalizedTerm = "n123456";
when(normalizer.apply(searchTerm)).thenReturn(Optional.of(normalizedTerm));

// when
var actual = lccnSearchTermProcessor.getSearchTerm(searchTerm);
assertThat(actual).isEqualTo("n123456");

// then
assertThat(actual).isEqualTo(normalizedTerm);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.folio.search.service.lccn;

import static org.assertj.core.api.Assertions.assertThat;

import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@UnitTest
class DefaultLccnNormalizerTest {
private DefaultLccnNormalizer lccnNormalizer = new DefaultLccnNormalizer();

@DisplayName("LCCN value normalization")
@CsvSource({"n 1234,n1234", " N 1234 ,n1234", "*1234,*1234", "1234*,1234*"})
@ParameterizedTest(name = "[{index}] value={0}, expected={1}")
void getLccnNormalized_parameterized(String value, String expected) {
var normalized = lccnNormalizer.apply(value);
assertThat(normalized).contains(expected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.folio.search.service.lccn;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@UnitTest
class LccnNormalizerStructurebTest {
private final LccnNormalizerStructureB lccnNormalizer = new LccnNormalizerStructureB();

@Test
void shouldRemoveSpaces() {
assertThat(lccnNormalizer.apply(" 2017000002")).isEqualTo(Optional.of("2017000002"));
assertThat(lccnNormalizer.apply("2017000002 ")).isEqualTo(Optional.of("2017000002"));
assertThat(lccnNormalizer.apply("2017 000002")).isEqualTo(Optional.of("2017000002"));
assertThat(lccnNormalizer.apply(" 20 17000 002 ")).isEqualTo(Optional.of("2017000002"));
}

@Test
void shouldRemoveForwardSlash() {
assertThat(lccnNormalizer.apply("2012425165//r75")).isEqualTo(Optional.of("2012425165"));
assertThat(lccnNormalizer.apply("2022139101/AC/r932")).isEqualTo(Optional.of("2022139101"));
}

@Test
void shouldRemoveHyphen() {
assertThat(lccnNormalizer.apply("2022-890351")).isEqualTo(Optional.of("2022890351"));
}

@Test
void shouldNormalizeSerialNumber() {
assertThat(lccnNormalizer.apply("2011-89035")).isEqualTo(Optional.of("2011089035"));
assertThat(lccnNormalizer.apply("2020-2")).isEqualTo(Optional.of("2020000002"));
}

@Test
void shouldNormalizeSpacesAndHyphenAndForwardSlash() {
assertThat(lccnNormalizer.apply(" 20 20-2 //r23/AC")).isEqualTo(Optional.of("2020000002"));
}

@ParameterizedTest
@ValueSource(strings = {
"01234567891", // more than 10 digits
"2020-2-34", // more than one hyphen
"A017000002", // non-digit character
"/2017000002", // slash in the beginning
"", // empty string
"202-0234334", // "-" is in third index (instead of forth)
"2020-", // "-" is the last character
})
void shouldReturnEmptyOptionalWhenLccnIsNotValid(String toNormalize) {
assertThat(lccnNormalizer.apply(toNormalize)).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@
import org.folio.search.domain.dto.Identifier;
import org.folio.search.integration.ReferenceDataService;
import org.folio.search.model.client.CqlQueryParam;
import org.folio.search.service.lccn.DefaultLccnNormalizer;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@UnitTest
@ExtendWith(MockitoExtension.class)
class LccnAuthorityProcessorTest {

@InjectMocks
private LccnAuthorityProcessor lccnProcessor;
@Mock
private ReferenceDataService referenceDataService;

@BeforeEach
void setup() {
var lccnNormalizer = new DefaultLccnNormalizer();
lccnProcessor = new LccnAuthorityProcessor(referenceDataService, lccnNormalizer);
}

@MethodSource("lccnDataProvider")
@DisplayName("getFieldValue_parameterized")
@ParameterizedTest(name = "[{index}] authority with {0}, expected={2}")
Expand Down
Loading

0 comments on commit 730a010

Please sign in to comment.