From b0b0c680e4f48c65e148b5f8b072b64bc560c265 Mon Sep 17 00:00:00 2001 From: Dmytro Krutii Date: Thu, 18 Jul 2024 18:01:12 +0300 Subject: [PATCH] MODSOURCE-783 add qualifier --- .../java/org/folio/dao/util/MatchField.java | 20 +++++ .../org/folio/dao/util/RecordDaoUtil.java | 75 ++++++++++++++++--- .../org/folio/services/RecordServiceImpl.java | 21 +++--- .../rest/impl/RecordsMatchingApiTest.java | 4 +- 4 files changed, 96 insertions(+), 24 deletions(-) diff --git a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java index 3c93b7057..4d5a7982d 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java @@ -1,6 +1,7 @@ package org.folio.dao.util; import org.folio.processing.value.Value; +import org.folio.rest.jaxrs.model.Filter; import org.marc4j.marc.impl.Verifier; /** @@ -15,6 +16,7 @@ public class MatchField { private final String ind2; private final String subfield; private final Value value; + private final QualifierMatch qualifierMatch; private final String fieldPath; public MatchField(String tag, String ind1, String ind2, String subfield, Value value) { @@ -24,6 +26,20 @@ public MatchField(String tag, String ind1, String ind2, String subfield, Value v this.subfield = subfield; this.value = value; this.fieldPath = tag + ind1 + ind2 + subfield; + this.qualifierMatch = null; + } + + public MatchField(String tag, String ind1, String ind2, String subfield, Value value, QualifierMatch qualifierMatch) { + this.tag = tag; + this.ind1 = ind1; + this.ind2 = ind2; + this.subfield = subfield; + this.value = value; + this.qualifierMatch = qualifierMatch; + this.fieldPath = tag + ind1 + ind2 + subfield; + } + + public record QualifierMatch(Filter.Qualifier qualifier, String value) { } public String getTag() { @@ -46,6 +62,10 @@ public Value getValue() { return value; } + public QualifierMatch getQualifierMatch() { + return qualifierMatch; + } + public boolean isControlField() { return Verifier.isControlField(tag); } diff --git a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java index 3202f44b8..1eb7c2b9c 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java @@ -51,6 +51,7 @@ public final class RecordDaoUtil { public static final String RECORD_NOT_FOUND_TEMPLATE = "Record with id '%s' was not found"; private static final String COMMA = ","; + private static final String LIKE_OPERATOR = "%"; private static final List DELETED_LEADER_RECORD_STATUS = Arrays.asList("d", "s", "x"); private RecordDaoUtil() {} @@ -77,6 +78,17 @@ public static Condition getExternalIdsCondition(List externalIds, IdType return getIdCondition(idType, idField -> idField.in(toUUIDs(externalIds))); } + /** + * Get {@link Condition} where in external list ids and {@link IdType} and match by qualifier value + * + * @param externalIds list of external id + * @param idType external id type + * @return condition + */ + public static Condition getExternalIdsConditionWithQualifier(List externalIds, IdType idType, MatchField.QualifierMatch qualifier) { + return getIdConditionWithQualifier(idType, idField -> idField.in(toUUIDs(externalIds)), qualifier); + } + /** * Count query by {@link Condition} * @@ -478,6 +490,18 @@ public static Condition filterRecordByExternalHridValues(List externalHr return RECORDS_LB.EXTERNAL_HRID.in(externalHridValues); } + /** + * Get {@link Condition} to filter by external entity hrid using specified values and match by qualifier value + * + * @param externalHridValues external entity hrid values to equal + * @return condition + */ + public static Condition filterRecordByExternalHridValuesWithQualifier(List externalHridValues, MatchField.QualifierMatch qualifier) { + var qualifierCondition = buildQualifierCondition(RECORDS_LB.EXTERNAL_HRID, qualifier); + var resultCondition = RECORDS_LB.EXTERNAL_HRID.in(externalHridValues); + return qualifierCondition != null ? resultCondition.and(qualifierCondition) : resultCondition; + } + /** * Get {@link Condition} to filter by snapshotId id * @@ -734,20 +758,47 @@ private static Condition getRecordTypeCondition(RecordType recordType) { } private static Condition getIdCondition(IdType idType, Function, Condition> idFieldToConditionMapper) { - IdType idTypeToUse = idType; - RecordType recordType = null; - if (idType == IdType.HOLDINGS) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_HOLDING; - } else if (idType == IdType.INSTANCE) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_BIB; - } else if (idType == IdType.AUTHORITY) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_AUTHORITY; - } + IdType idTypeToUse = getIdType(idType); + RecordType recordType = getRecordType(idTypeToUse); var idField = RECORDS_LB.field(LOWER_CAMEL.to(LOWER_UNDERSCORE, idTypeToUse.getIdField()), UUID.class); return idFieldToConditionMapper.apply(idField).and(getRecordTypeCondition(recordType)); } + private static Condition getIdConditionWithQualifier(IdType idType, Function, Condition> idFieldToConditionMapper, MatchField.QualifierMatch qualifier) { + IdType idTypeToUse = getIdType(idType); + RecordType recordType = getRecordType(idTypeToUse); + var idField = RECORDS_LB.field(LOWER_CAMEL.to(LOWER_UNDERSCORE, idTypeToUse.getIdField()), UUID.class); + var qualifierCondition = buildQualifierCondition(idField, qualifier); + var resultCondition = idFieldToConditionMapper.apply(idField).and(getRecordTypeCondition(recordType)); + return qualifierCondition != null ? resultCondition.and(qualifierCondition) : resultCondition; + } + + private static Condition buildQualifierCondition(Field field, MatchField.QualifierMatch qualifier) { + if (qualifier == null) { + return null; + } + var value = qualifier.value(); + return switch (qualifier.qualifier()) { + case BEGINS_WITH -> field.like(value + LIKE_OPERATOR); + case ENDS_WITH -> field.like(LIKE_OPERATOR + value); + case CONTAINS -> field.like(LIKE_OPERATOR + value + LIKE_OPERATOR); + }; + } + + private static RecordType getRecordType(IdType idType) { + return switch (idType) { + case HOLDINGS -> RecordType.MARC_HOLDING; + case INSTANCE -> RecordType.MARC_BIB; + case AUTHORITY -> RecordType.MARC_AUTHORITY; + default -> null; + }; + } + + private static IdType getIdType(IdType idType) { + return switch (idType) { + case HOLDINGS, INSTANCE, AUTHORITY -> IdType.EXTERNAL; + default -> idType; + }; + } + } diff --git a/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java b/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java index b47400c15..43f8e2f3b 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java @@ -4,13 +4,7 @@ import static java.util.Objects.nonNull; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; -import static org.folio.dao.util.RecordDaoUtil.RECORD_NOT_FOUND_TEMPLATE; -import static org.folio.dao.util.RecordDaoUtil.ensureRecordForeignKeys; -import static org.folio.dao.util.RecordDaoUtil.ensureRecordHasId; -import static org.folio.dao.util.RecordDaoUtil.ensureRecordHasSuppressDiscovery; -import static org.folio.dao.util.RecordDaoUtil.filterRecordByExternalHridValues; -import static org.folio.dao.util.RecordDaoUtil.filterRecordByState; -import static org.folio.dao.util.RecordDaoUtil.getExternalIdsCondition; +import static org.folio.dao.util.RecordDaoUtil.*; import static org.folio.dao.util.SnapshotDaoUtil.SNAPSHOT_NOT_FOUND_TEMPLATE; import static org.folio.dao.util.SnapshotDaoUtil.SNAPSHOT_NOT_STARTED_MESSAGE_TEMPLATE; import static org.folio.rest.util.QueryParamUtil.toRecordType; @@ -462,7 +456,11 @@ private MatchField prepareMatchField(RecordMatchingDto recordMatchingDto) { String ind1 = filter.getIndicator1() != null ? filter.getIndicator1() : StringUtils.EMPTY; String ind2 = filter.getIndicator2() != null ? filter.getIndicator2() : StringUtils.EMPTY; String subfield = filter.getSubfield() != null ? filter.getSubfield() : StringUtils.EMPTY; - return new MatchField(filter.getField(), ind1, ind2, subfield, ListValue.of(filter.getValues())); + MatchField.QualifierMatch qualifier = null; + if (filter.getQualifier() != null && filter.getQualifierValue() != null) { + qualifier = new MatchField.QualifierMatch(filter.getQualifier(), filter.getQualifierValue()); + } + return new MatchField(filter.getField(), ind1, ind2, subfield, ListValue.of(filter.getValues()), qualifier); } private TypeConnection getTypeConnection(RecordMatchingDto.RecordType recordType) { @@ -477,13 +475,14 @@ private Future processDefaultMatchField(MatchField RecordMatchingDto recordMatchingDto, String tenantId) { Condition condition = filterRecordByState(Record.State.ACTUAL.value()); List values = ((ListValue) matchField.getValue()).getValue(); + var qualifier = matchField.getQualifierMatch(); if (matchField.isMatchedId()) { - condition = condition.and(getExternalIdsCondition(values, IdType.RECORD)); + condition = condition.and(getExternalIdsConditionWithQualifier(values, IdType.RECORD, qualifier)); } else if (matchField.isExternalId()) { - condition = condition.and(getExternalIdsCondition(values, IdType.EXTERNAL)); + condition = condition.and(getExternalIdsConditionWithQualifier(values, IdType.EXTERNAL, qualifier)); } else if (matchField.isExternalHrid()) { - condition = condition.and(filterRecordByExternalHridValues(values)); + condition = condition.and(filterRecordByExternalHridValuesWithQualifier(values, qualifier)); } return recordDao.getRecords(condition, typeConnection.getDbType(), Collections.emptyList(), recordMatchingDto.getOffset(), diff --git a/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java b/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java index 455549408..1c654ace1 100644 --- a/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java +++ b/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java @@ -100,7 +100,9 @@ public void shouldReturnEmptyCollectionIfRecordsDoNotMatch() { .withField("999") .withIndicator1("f") .withIndicator2("f") - .withSubfield("s")))) + .withSubfield("s") + .withQualifier(Filter.Qualifier.BEGINS_WITH) + .withQualifierValue("TEST")))) .post(RECORDS_MATCHING_PATH) .then() .statusCode(HttpStatus.SC_OK)