From 20421465544eb005034a1d55a21888fde2fd8bfd Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Thu, 22 Aug 2024 13:03:48 +0300 Subject: [PATCH 01/12] feature/MODELINKS-248 Extend-authorities --- NEWS.md | 1 + descriptors/ModuleDescriptor-template.json | 2 +- .../controller/converter/AuthorityMapper.java | 2 + .../converter/AuthorityUtilityMapper.java | 50 +++++++ .../entlinks/domain/entity/AuthorityBase.java | 8 + .../domain/entity/AuthorityConstants.java | 12 ++ .../db/changelog/changelog-master.xml | 2 + ...additional-fields-to-authority-archive.xml | 19 +++ .../add-additional-fields-to-authority.xml | 19 +++ .../authority-storage/authorityDto.yaml | 30 ++++ .../converter/AuthorityUtilityMapperTest.java | 138 ++++++++++++++++++ .../authority/AuthorityServiceTest.java | 12 ++ 12 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/NEWS.md b/NEWS.md index dbfca9b3..e7284d80 100644 --- a/NEWS.md +++ b/NEWS.md @@ -66,6 +66,7 @@ * Fix authority record update and `updatedByUserId` field assignment ([MODELINKS-219](https://issues.folio.org/browse/MODELINKS-219)) * Fix saving of Authority file with empty Base URL when another Authority file with empty Base URL already exists ([MODELINKS-216](https://issues.folio.org/browse/MODELINKS-216)) * Fix handling of authority heading type change update event ([MODELINKS-242](https://issues.folio.org/browse/MODELINKS-242)) +* Extend authorities with additional fields for Advanced References Classification ([MODELINKS-248](https://issues.folio.org/browse/MODELINKS-248)) ### Tech Dept * Create custom Mockito verifies for Hibernate entities ([MODELINKS-209](https://issues.folio.org/browse/MODELINKS-209)) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index c20e2561..b03ee397 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -167,7 +167,7 @@ }, { "id": "authority-storage", - "version": "2.1", + "version": "2.2", "handlers": [ { "methods": [ diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 5c4c49f5..21aeb963 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,6 +93,7 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -100,6 +101,7 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java index 3addfb2c..7a3522bd 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java @@ -2,14 +2,20 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; @@ -148,6 +154,29 @@ public static void extractAuthoritySaftHeadings(AuthorityDto source, AuthorityBa target.setSaftHeadings(saftHeadings); } + public static void extractAuthorityAdditionalHeadings(AuthorityDto source, AuthorityBase target) { + List additionalHeadings = new ArrayList<>(); + if (isNotEmpty(source.getBroaderTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getBroaderTerm(), BROADER_TERM)); + } + if (isNotEmpty(source.getNarrowerTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getNarrowerTerm(), NARROWER_TERM)); + } + if (isNotEmpty(source.getEarlierHeading())) { + additionalHeadings.addAll(asSftHeadings(source.getEarlierHeading(), EARLIER_HEADING)); + } + if (isNotEmpty(source.getLaterHeading())) { + additionalHeadings.addAll(asSftHeadings(source.getLaterHeading(), LATER_HEADING)); + } + if (isNotEmpty(source.getSftTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getSftTerm(), SFT_TERM)); + } + if (isNotEmpty(source.getSaftTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getSaftTerm(), SAFT_TERM)); + } + target.setAdditionalHeadings(additionalHeadings); + } + public static void extractAuthorityDtoHeadingValue(AuthorityBase source, AuthorityDto target) { if (source.getHeadingType() == null || source.getHeading() == null) { return; @@ -181,6 +210,12 @@ public static void extractAuthorityDtoSaftHeadings(AuthorityBase source, Authori source.getSaftHeadings().forEach(headingRef -> extractAuthorityDtoSaftHeading(headingRef, target)); } + public static void extractAuthorityDtoAdditionalHeadings(AuthorityBase source, AuthorityDto target) { + if (isNotEmpty(source.getAdditionalHeadings())) { + source.getAdditionalHeadings().forEach(headingRef -> extractAuthorityDtoAdditionalHeading(headingRef, target)); + } + } + private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto target) { if (headingRef == null || headingRef.getHeadingType() == null) { return; @@ -224,4 +259,19 @@ private static List asSftHeadings(List headingValues, String .map(headingValue -> new HeadingRef(headingType, headingValue)) .toList(); } + + private void extractAuthorityDtoAdditionalHeading(HeadingRef headingRef, AuthorityDto target) { + if (headingRef == null || headingRef.getHeadingType() == null) { + return; + } + switch (headingRef.getHeadingType()) { + case BROADER_TERM -> target.addBroaderTermItem(headingRef.getHeading()); + case NARROWER_TERM -> target.addNarrowerTermItem(headingRef.getHeading()); + case EARLIER_HEADING -> target.addEarlierHeadingItem(headingRef.getHeading()); + case LATER_HEADING -> target.addLaterHeadingItem(headingRef.getHeading()); + case SAFT_TERM -> target.addSaftTermItem(headingRef.getHeading()); + case SFT_TERM -> target.addSftTermItem(headingRef.getHeading()); + default -> log.warn("Invalid additional heading type - {} cannot be mapped", headingRef.getHeadingType()); + } + } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index dee2764e..2d7b0a9f 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,6 +42,7 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; + private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -90,6 +91,10 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; + @Column(name = ADDITIONAL_HEADINGS_COLUMN) + @JdbcTypeCode(SqlTypes.JSON) + private List additionalHeadings; + public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -116,6 +121,9 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; + this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() + .map(HeadingRef::new) + .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index 337529a3..feb80395 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,4 +24,16 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; + + public static final String BROADER_TERM = "broaderTerm"; + + public static final String NARROWER_TERM = "narrowerTerm"; + + public static final String EARLIER_HEADING = "earlierHeading"; + + public static final String LATER_HEADING = "laterHeading"; + + public static final String SFT_TERM = "sftTerm"; + + public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 906c7bf5..a4417124 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,4 +23,6 @@ + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml new file mode 100644 index 00000000..404ed022 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority_archive table + + + + + + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml new file mode 100644 index 00000000..80db8253 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority table + + + + + + + diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index c3c3a6ee..aeb4a103 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -163,6 +163,36 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' + broaderTerm: + type: array + description: Terms that represents broader, more general concepts related to the authority record + items: + type: string + narrowerTerm: + type: array + description: Terms that represents narrower, more specific concepts derived from the authority record + items: + type: string + earlierHeading: + type: array + description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + laterHeading: + type: array + description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string + sftTerm: + type: array + description: See from tracing term + items: + type: string + saftTerm: + type: array + description: See also from tracing term + items: + type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java index fd7d5ab4..badc1409 100644 --- a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java +++ b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java @@ -2,17 +2,25 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; +import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import static org.folio.support.base.TestConstants.TEST_STRING; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -24,6 +32,7 @@ import org.folio.entlinks.domain.entity.Authority; import org.folio.entlinks.domain.entity.HeadingRef; 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.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -181,6 +190,124 @@ void testExtractAuthorityDtoHeadingValue(String headingType, String headingValue } } + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityAdditionalHeadingsWithNonNullValues(String propertyType, List propertyValues) { + switch (propertyType) { + case BROADER_TERM -> source.setBroaderTerm(propertyValues); + case NARROWER_TERM -> source.setNarrowerTerm(propertyValues); + case EARLIER_HEADING -> source.setEarlierHeading(propertyValues); + case LATER_HEADING -> source.setLaterHeading(propertyValues); + case SAFT_TERM -> source.setSaftTerm(propertyValues); + case SFT_TERM -> source.setSftTerm(propertyValues); + default -> fail("Invalid heading type - {} cannot be mapped", propertyType); + } + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + List additionalHeadings = target.getAdditionalHeadings(); + String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); + assertThat(additionalHeadings).hasSize(propertyValues.size()); + additionalHeadings.forEach(a -> assertEquals(propertyType, a.getHeadingType())); + assertArrayEquals(propertyValues.toArray(), targetHeadingValues); + } + + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityAdditionalHeadingsWithNullValues(String propertyType, List propertyValues) { + switch (propertyType) { + case BROADER_TERM -> source.setBroaderTerm(null); + case NARROWER_TERM -> source.setNarrowerTerm(null); + case EARLIER_HEADING -> source.setEarlierHeading(null); + case LATER_HEADING -> source.setLaterHeading(null); + case SAFT_TERM -> source.setSaftTerm(null); + case SFT_TERM -> source.setSftTerm(null); + default -> fail("Invalid heading type - {} cannot be mapped", propertyType); + } + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + assertThat(target.getAdditionalHeadings()).isEmpty(); + } + + @Test + void testExtractAuthorityAdditionalHeadingsWithMixedHeadingTypes() { + source.setBroaderTerm(List.of("boarderTerm1", "boarderTerm2")); + source.setNarrowerTerm(List.of("narrowerTerm")); + source.setEarlierHeading(List.of("earlierHeading")); + source.setLaterHeading(List.of("laterHeading")); + source.setSftTerm(List.of("sftTerm1", "sftTerm2", "sftTerm3")); + source.setSaftTerm(List.of("saftTerm1", "saftTerm2")); + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + List additionalHeadings = target.getAdditionalHeadings(); + String[] targetHeadingTypes = additionalHeadings.stream().map(HeadingRef::getHeadingType).toArray(String[]::new); + String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); + assertThat(additionalHeadings).hasSize(10); + assertArrayEquals(new String[]{BROADER_TERM, BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING, SFT_TERM, + SFT_TERM, SFT_TERM, SAFT_TERM, SAFT_TERM}, targetHeadingTypes); + assertArrayEquals(new String[]{"boarderTerm1", "boarderTerm2", "narrowerTerm", "earlierHeading", "laterHeading", + "sftTerm1", "sftTerm2", "sftTerm3", "saftTerm1", "saftTerm2"}, targetHeadingValues); + } + + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityDtoAdditionalHeadingsWithNonNullValues(String headingType, List headingValues) { + List additionalHeadings = headingValues.stream().map(hv -> new HeadingRef(headingType, hv)).toList(); + target.setAdditionalHeadings(additionalHeadings); + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + switch (headingType) { + case BROADER_TERM -> assertArrayEquals(source.getBroaderTerm().toArray(), headingValues.toArray()); + case NARROWER_TERM -> assertArrayEquals(source.getNarrowerTerm().toArray(), headingValues.toArray()); + case EARLIER_HEADING -> assertArrayEquals(source.getEarlierHeading().toArray(), headingValues.toArray()); + case LATER_HEADING -> assertArrayEquals(source.getLaterHeading().toArray(), headingValues.toArray()); + case SFT_TERM -> assertArrayEquals(source.getSftTerm().toArray(), headingValues.toArray()); + case SAFT_TERM -> assertArrayEquals(source.getSaftTerm().toArray(), headingValues.toArray()); + default -> fail("Invalid saft heading type - {} cannot be mapped", headingType); + } + } + + @Test + void testExtractAuthorityDtoAdditionalHeadingsWithNullValues() { + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + assertTrue(source.getBroaderTerm().isEmpty()); + assertTrue(source.getNarrowerTerm().isEmpty()); + assertTrue(source.getEarlierHeading().isEmpty()); + assertTrue(source.getLaterHeading().isEmpty()); + assertTrue(source.getSftTerm().isEmpty()); + assertTrue(source.getSaftTerm().isEmpty()); + } + + @Test + void testExtractAuthorityDtoAdditionalHeadingsWithMixedHeadingTypes() { + List additionalHeadings = new ArrayList<>(); + additionalHeadings.add(new HeadingRef(BROADER_TERM, "broaderTerm")); + additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm1")); + additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm2")); + additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading1")); + additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading2")); + additionalHeadings.add(new HeadingRef(LATER_HEADING, "laterHeading")); + additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm1")); + additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm2")); + additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm1")); + additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm2")); + target.setAdditionalHeadings(additionalHeadings); + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + assertArrayEquals(new String[] {"broaderTerm"}, source.getBroaderTerm().toArray()); + assertArrayEquals(new String[] {"narrowerTerm1", "narrowerTerm2"}, source.getNarrowerTerm().toArray()); + assertArrayEquals(new String[] {"earlierHeading1", "earlierHeading2"}, source.getEarlierHeading().toArray()); + assertArrayEquals(new String[] {"laterHeading"}, source.getLaterHeading().toArray()); + assertArrayEquals(new String[] {"sftTerm1", "sftTerm2"}, source.getSftTerm().toArray()); + assertArrayEquals(new String[] {"saftTerm1", "saftTerm2"}, source.getSaftTerm().toArray()); + } + private static Stream headingTypeAndValueProvider() { return Stream.of( arguments(PERSONAL_NAME_HEADING, TEST_STRING), @@ -196,4 +323,15 @@ private static Stream headingTypeAndValueProvider() { ); } + private static Stream additionalHeadingTypeAndValuesProvider() { + return Stream.of( + arguments(BROADER_TERM, List.of(TEST_STRING)), + arguments(NARROWER_TERM, List.of(TEST_STRING, TEST_STRING)), + arguments(EARLIER_HEADING, List.of(TEST_STRING)), + arguments(LATER_HEADING, List.of(TEST_STRING, TEST_STRING)), + arguments(SFT_TERM, List.of(TEST_STRING, TEST_STRING)), + arguments(SAFT_TERM, List.of(TEST_STRING, TEST_STRING, TEST_STRING)), + arguments(SAFT_TERM, List.of()) + ); + } } diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 412a69c3..79e12863 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,6 +130,12 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); + existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), + new HeadingRef("narrowerTerm", "narrower"), + new HeadingRef("earlierHeading", "earlier"), + new HeadingRef("laterHeading", "later"), + new HeadingRef("sftTerm", "sft"), + new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -145,6 +151,12 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); + modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), + new HeadingRef("narrowerTerm", "narrowerNew"), + new HeadingRef("earlierHeading", "earlierNew"), + new HeadingRef("laterHeading", "laterNew"), + new HeadingRef("sftTerm", "sftNew"), + new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew); From 31070bd6cfff77f5c335d3212aa58171f3197281 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 30 Aug 2024 16:05:47 +0300 Subject: [PATCH 02/12] feature/MODELINKS-248 split relationship fields to sft and saft --- .../controller/converter/AuthorityMapper.java | 2 - .../converter/AuthorityUtilityMapper.java | 116 +++++++---- .../entlinks/domain/entity/AuthorityBase.java | 8 - .../domain/entity/AuthorityConstants.java | 12 -- .../entlinks/domain/entity/HeadingRef.java | 11 ++ .../domain/entity/RelationshipType.java | 6 + .../db/changelog/changelog-master.xml | 2 - ...additional-fields-to-authority-archive.xml | 19 -- .../add-additional-fields-to-authority.xml | 19 -- .../authority-storage/authorityDto.yaml | 70 ++++--- .../converter/AuthorityUtilityMapperTest.java | 186 +++++++----------- .../authority/AuthorityServiceTest.java | 12 -- 12 files changed, 200 insertions(+), 263 deletions(-) create mode 100644 src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 21aeb963..5c4c49f5 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,7 +93,6 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -101,7 +100,6 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java index 7a3522bd..18621c80 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java @@ -2,31 +2,28 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; import org.folio.entlinks.domain.dto.AuthorityDto; import org.folio.entlinks.domain.entity.AuthorityBase; import org.folio.entlinks.domain.entity.HeadingRef; +import org.folio.entlinks.domain.entity.RelationshipType; @UtilityClass @Log4j2 @@ -116,6 +113,7 @@ public static void extractAuthoritySftHeadings(AuthorityDto source, AuthorityBas if (isNotEmpty(source.getSftGenreTerm())) { sftHeadings.addAll(asSftHeadings(source.getSftGenreTerm(), GENRE_TERM_HEADING)); } + addRelationshipsToSftHeadings(source, sftHeadings); target.setSftHeadings(sftHeadings); } @@ -151,30 +149,34 @@ public static void extractAuthoritySaftHeadings(AuthorityDto source, AuthorityBa if (isNotEmpty(source.getSaftGenreTerm())) { saftHeadings.addAll(asSftHeadings(source.getSaftGenreTerm(), GENRE_TERM_HEADING)); } + addRelationshipsToSaftHeadings(source, saftHeadings); target.setSaftHeadings(saftHeadings); } - public static void extractAuthorityAdditionalHeadings(AuthorityDto source, AuthorityBase target) { - List additionalHeadings = new ArrayList<>(); - if (isNotEmpty(source.getBroaderTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getBroaderTerm(), BROADER_TERM)); - } - if (isNotEmpty(source.getNarrowerTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getNarrowerTerm(), NARROWER_TERM)); - } - if (isNotEmpty(source.getEarlierHeading())) { - additionalHeadings.addAll(asSftHeadings(source.getEarlierHeading(), EARLIER_HEADING)); - } - if (isNotEmpty(source.getLaterHeading())) { - additionalHeadings.addAll(asSftHeadings(source.getLaterHeading(), LATER_HEADING)); - } - if (isNotEmpty(source.getSftTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getSftTerm(), SFT_TERM)); - } - if (isNotEmpty(source.getSaftTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getSaftTerm(), SAFT_TERM)); + private static void addRelationshipsToSftHeadings(final AuthorityDto source, final List headingRefs) { + processRelationshipHeadings(source.getSftBroaderTerm(), headingRefs, RelationshipType.BROADER_TERM); + processRelationshipHeadings(source.getSftNarrowerTerm(), headingRefs, RelationshipType.NARROWER_TERM); + processRelationshipHeadings(source.getSftEarlierHeading(), headingRefs, RelationshipType.EARLIER_HEADING); + processRelationshipHeadings(source.getSftLaterHeading(), headingRefs, RelationshipType.LATER_HEADING); + } + + private static void addRelationshipsToSaftHeadings(final AuthorityDto source, final List headingRefs) { + processRelationshipHeadings(source.getSaftBroaderTerm(), headingRefs, RelationshipType.BROADER_TERM); + processRelationshipHeadings(source.getSaftNarrowerTerm(), headingRefs, RelationshipType.NARROWER_TERM); + processRelationshipHeadings(source.getSaftEarlierHeading(), headingRefs, RelationshipType.EARLIER_HEADING); + processRelationshipHeadings(source.getSaftLaterHeading(), headingRefs, RelationshipType.LATER_HEADING); + } + + private static void processRelationshipHeadings(List relationshipHeadings, final List headingRefs, + final RelationshipType relationshipType) { + if (isNotEmpty(relationshipHeadings)) { + headingRefs.forEach(headingRef -> { + if (relationshipHeadings.contains(headingRef.getHeading())) { + Set relationshipTypeSet = getOrCreateRelationshipTypeSet(headingRef); + relationshipTypeSet.add(relationshipType); + } + }); } - target.setAdditionalHeadings(additionalHeadings); } public static void extractAuthorityDtoHeadingValue(AuthorityBase source, AuthorityDto target) { @@ -210,12 +212,6 @@ public static void extractAuthorityDtoSaftHeadings(AuthorityBase source, Authori source.getSaftHeadings().forEach(headingRef -> extractAuthorityDtoSaftHeading(headingRef, target)); } - public static void extractAuthorityDtoAdditionalHeadings(AuthorityBase source, AuthorityDto target) { - if (isNotEmpty(source.getAdditionalHeadings())) { - source.getAdditionalHeadings().forEach(headingRef -> extractAuthorityDtoAdditionalHeading(headingRef, target)); - } - } - private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto target) { if (headingRef == null || headingRef.getHeadingType() == null) { return; @@ -233,6 +229,7 @@ private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto t case GENRE_TERM_HEADING -> target.addSftGenreTermItem(headingRef.getHeading()); default -> log.warn("Invalid sft heading type - {} cannot be mapped", headingRef.getHeadingType()); } + extractSftHeadingsRelationships(headingRef, target); } private void extractAuthorityDtoSaftHeading(HeadingRef headingRef, AuthorityDto target) { @@ -252,6 +249,39 @@ private void extractAuthorityDtoSaftHeading(HeadingRef headingRef, AuthorityDto case GENRE_TERM_HEADING -> target.addSaftGenreTermItem(headingRef.getHeading()); default -> log.warn("Invalid saft heading type - {} cannot be mapped", headingRef.getHeadingType()); } + extractSaftHeadingsRelationships(headingRef, target); + } + + private static void extractSftHeadingsRelationships(HeadingRef headingRef, AuthorityDto target) { + if (isNotEmpty(headingRef.getRelationshipType())) { + headingRef.getRelationshipType().forEach( + relationshipType -> { + switch (relationshipType) { + case BROADER_TERM -> addIfNotExists(target.getSftBroaderTerm(), headingRef.getHeading()); + case NARROWER_TERM -> addIfNotExists(target.getSftNarrowerTerm(), headingRef.getHeading()); + case EARLIER_HEADING -> addIfNotExists(target.getSftEarlierHeading(), headingRef.getHeading()); + case LATER_HEADING -> addIfNotExists(target.getSftLaterHeading(), headingRef.getHeading()); + default -> log.warn("Invalid sft relationship type - {} cannot be mapped", relationshipType); + } + } + ); + } + } + + private static void extractSaftHeadingsRelationships(HeadingRef headingRef, AuthorityDto target) { + if (isNotEmpty(headingRef.getRelationshipType())) { + headingRef.getRelationshipType().forEach( + relationshipType -> { + switch (relationshipType) { + case BROADER_TERM -> addIfNotExists(target.getSaftBroaderTerm(), headingRef.getHeading()); + case NARROWER_TERM -> addIfNotExists(target.getSaftNarrowerTerm(), headingRef.getHeading()); + case EARLIER_HEADING -> addIfNotExists(target.getSaftEarlierHeading(), headingRef.getHeading()); + case LATER_HEADING -> addIfNotExists(target.getSaftLaterHeading(), headingRef.getHeading()); + default -> log.warn("Invalid saft relationship type - {} cannot be mapped", relationshipType); + } + } + ); + } } private static List asSftHeadings(List headingValues, String headingType) { @@ -260,18 +290,18 @@ private static List asSftHeadings(List headingValues, String .toList(); } - private void extractAuthorityDtoAdditionalHeading(HeadingRef headingRef, AuthorityDto target) { - if (headingRef == null || headingRef.getHeadingType() == null) { - return; + private static Set getOrCreateRelationshipTypeSet(HeadingRef heading) { + Set relationshipTypeSet = heading.getRelationshipType(); + if (relationshipTypeSet == null) { + relationshipTypeSet = new HashSet<>(); + heading.setRelationshipType(relationshipTypeSet); } - switch (headingRef.getHeadingType()) { - case BROADER_TERM -> target.addBroaderTermItem(headingRef.getHeading()); - case NARROWER_TERM -> target.addNarrowerTermItem(headingRef.getHeading()); - case EARLIER_HEADING -> target.addEarlierHeadingItem(headingRef.getHeading()); - case LATER_HEADING -> target.addLaterHeadingItem(headingRef.getHeading()); - case SAFT_TERM -> target.addSaftTermItem(headingRef.getHeading()); - case SFT_TERM -> target.addSftTermItem(headingRef.getHeading()); - default -> log.warn("Invalid additional heading type - {} cannot be mapped", headingRef.getHeadingType()); + return relationshipTypeSet; + } + + private static void addIfNotExists(List headings, String heading) { + if (!headings.contains(heading)) { + headings.add(heading); } } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index 2d7b0a9f..dee2764e 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,7 +42,6 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; - private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -91,10 +90,6 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; - @Column(name = ADDITIONAL_HEADINGS_COLUMN) - @JdbcTypeCode(SqlTypes.JSON) - private List additionalHeadings; - public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -121,9 +116,6 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; - this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() - .map(HeadingRef::new) - .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index feb80395..337529a3 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,16 +24,4 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; - - public static final String BROADER_TERM = "broaderTerm"; - - public static final String NARROWER_TERM = "narrowerTerm"; - - public static final String EARLIER_HEADING = "earlierHeading"; - - public static final String LATER_HEADING = "laterHeading"; - - public static final String SFT_TERM = "sftTerm"; - - public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java b/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java index bf9b72a1..f74fc8f6 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java +++ b/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java @@ -1,6 +1,8 @@ package org.folio.entlinks.domain.entity; +import com.fasterxml.jackson.annotation.JsonInclude; import java.io.Serializable; +import java.util.Set; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -14,14 +16,23 @@ @AllArgsConstructor @ToString @EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_NULL) public class HeadingRef implements Serializable { private String headingType; private String heading; + private Set relationshipType; + public HeadingRef(HeadingRef other) { this.heading = other.heading; this.headingType = other.headingType; + this.relationshipType = other.relationshipType; + } + + public HeadingRef(String headingType, String heading) { + this.headingType = headingType; + this.heading = heading; } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java b/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java new file mode 100644 index 00000000..b830b5b5 --- /dev/null +++ b/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java @@ -0,0 +1,6 @@ +package org.folio.entlinks.domain.entity; + +public enum RelationshipType { + + BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING +} diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index a4417124..906c7bf5 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,6 +23,4 @@ - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml deleted file mode 100644 index 404ed022..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority_archive table - - - - - - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml deleted file mode 100644 index 80db8253..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority table - - - - - - - diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index aeb4a103..9e67295e 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -110,6 +110,26 @@ properties: description: See from tracing topical term items: type: string + sftBroaderTerm: + type: array + description: See from tracing term that represents broader, more general concepts related to the authority record + items: + type: string + sftNarrowerTerm: + type: array + description: See from tracing term that that represents narrower, more specific concepts derived from the authority record + items: + type: string + sftEarlierHeading: + type: array + description: See from tracing heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + sftLaterHeading: + type: array + description: See from tracing heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string saftTopicalTerm: type: array description: See also from tracing topical term @@ -144,6 +164,26 @@ properties: description: See also from tracing genre/form term items: type: string + saftBroaderTerm: + type: array + description: See also from tracing term that represents broader, more general concepts related to the authority record + items: + type: string + saftNarrowerTerm: + type: array + description: See also from tracing term that that represents narrower, more specific concepts derived from the authority record + items: + type: string + saftEarlierHeading: + type: array + description: See also from tracing heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + saftLaterHeading: + type: array + description: See also from tracing heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string identifiers: type: array description: An extensible set of name-value pairs of identifiers associated with the resource @@ -163,36 +203,6 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' - broaderTerm: - type: array - description: Terms that represents broader, more general concepts related to the authority record - items: - type: string - narrowerTerm: - type: array - description: Terms that represents narrower, more specific concepts derived from the authority record - items: - type: string - earlierHeading: - type: array - description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. - items: - type: string - laterHeading: - type: array - description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. - items: - type: string - sftTerm: - type: array - description: See from tracing term - items: - type: string - saftTerm: - type: array - description: See also from tracing term - items: - type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java index badc1409..29b838f5 100644 --- a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java +++ b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java @@ -2,20 +2,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; -import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import static org.folio.support.base.TestConstants.TEST_STRING; @@ -27,10 +21,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.folio.entlinks.domain.dto.AuthorityDto; import org.folio.entlinks.domain.entity.Authority; import org.folio.entlinks.domain.entity.HeadingRef; +import org.folio.entlinks.domain.entity.RelationshipType; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -190,122 +186,50 @@ void testExtractAuthorityDtoHeadingValue(String headingType, String headingValue } } - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityAdditionalHeadingsWithNonNullValues(String propertyType, List propertyValues) { - switch (propertyType) { - case BROADER_TERM -> source.setBroaderTerm(propertyValues); - case NARROWER_TERM -> source.setNarrowerTerm(propertyValues); - case EARLIER_HEADING -> source.setEarlierHeading(propertyValues); - case LATER_HEADING -> source.setLaterHeading(propertyValues); - case SAFT_TERM -> source.setSaftTerm(propertyValues); - case SFT_TERM -> source.setSftTerm(propertyValues); - default -> fail("Invalid heading type - {} cannot be mapped", propertyType); - } + @Test + void testExtractAuthoritySftHeadingsWithRelationships() { + final AuthorityDto authorityDto = getAuthorityDtoWithSftTerms(); + final List expectedHeadingRefs = getHeadingRefs(); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + AuthorityUtilityMapper.extractAuthoritySftHeadings(authorityDto, target); - List additionalHeadings = target.getAdditionalHeadings(); - String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); - assertThat(additionalHeadings).hasSize(propertyValues.size()); - additionalHeadings.forEach(a -> assertEquals(propertyType, a.getHeadingType())); - assertArrayEquals(propertyValues.toArray(), targetHeadingValues); + List sftHeadings = target.getSftHeadings(); + assertThat(sftHeadings).hasSize(11); + assertArrayEquals(expectedHeadingRefs.toArray(), sftHeadings.toArray()); } - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityAdditionalHeadingsWithNullValues(String propertyType, List propertyValues) { - switch (propertyType) { - case BROADER_TERM -> source.setBroaderTerm(null); - case NARROWER_TERM -> source.setNarrowerTerm(null); - case EARLIER_HEADING -> source.setEarlierHeading(null); - case LATER_HEADING -> source.setLaterHeading(null); - case SAFT_TERM -> source.setSaftTerm(null); - case SFT_TERM -> source.setSftTerm(null); - default -> fail("Invalid heading type - {} cannot be mapped", propertyType); - } + @Test + void testExtractAuthorityDtoSftHeadingsWithRelationships() { + final List sftHeadings = getHeadingRefs(); + target.setSftHeadings(sftHeadings); + final AuthorityDto expectedAuthorityDto = getAuthorityDtoWithSftTerms(); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(target, source); - assertThat(target.getAdditionalHeadings()).isEmpty(); + assertEquals(expectedAuthorityDto, source); } @Test - void testExtractAuthorityAdditionalHeadingsWithMixedHeadingTypes() { - source.setBroaderTerm(List.of("boarderTerm1", "boarderTerm2")); - source.setNarrowerTerm(List.of("narrowerTerm")); - source.setEarlierHeading(List.of("earlierHeading")); - source.setLaterHeading(List.of("laterHeading")); - source.setSftTerm(List.of("sftTerm1", "sftTerm2", "sftTerm3")); - source.setSaftTerm(List.of("saftTerm1", "saftTerm2")); - - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); - - List additionalHeadings = target.getAdditionalHeadings(); - String[] targetHeadingTypes = additionalHeadings.stream().map(HeadingRef::getHeadingType).toArray(String[]::new); - String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); - assertThat(additionalHeadings).hasSize(10); - assertArrayEquals(new String[]{BROADER_TERM, BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING, SFT_TERM, - SFT_TERM, SFT_TERM, SAFT_TERM, SAFT_TERM}, targetHeadingTypes); - assertArrayEquals(new String[]{"boarderTerm1", "boarderTerm2", "narrowerTerm", "earlierHeading", "laterHeading", - "sftTerm1", "sftTerm2", "sftTerm3", "saftTerm1", "saftTerm2"}, targetHeadingValues); - } - - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityDtoAdditionalHeadingsWithNonNullValues(String headingType, List headingValues) { - List additionalHeadings = headingValues.stream().map(hv -> new HeadingRef(headingType, hv)).toList(); - target.setAdditionalHeadings(additionalHeadings); + void testExtractAuthoritySaftHeadingsWithRelationships() { + final AuthorityDto authorityDto = getAuthorityDtoWithSaftTerms(); + final List expectedHeadingRefs = getHeadingRefs(); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + AuthorityUtilityMapper.extractAuthoritySaftHeadings(authorityDto, target); - switch (headingType) { - case BROADER_TERM -> assertArrayEquals(source.getBroaderTerm().toArray(), headingValues.toArray()); - case NARROWER_TERM -> assertArrayEquals(source.getNarrowerTerm().toArray(), headingValues.toArray()); - case EARLIER_HEADING -> assertArrayEquals(source.getEarlierHeading().toArray(), headingValues.toArray()); - case LATER_HEADING -> assertArrayEquals(source.getLaterHeading().toArray(), headingValues.toArray()); - case SFT_TERM -> assertArrayEquals(source.getSftTerm().toArray(), headingValues.toArray()); - case SAFT_TERM -> assertArrayEquals(source.getSaftTerm().toArray(), headingValues.toArray()); - default -> fail("Invalid saft heading type - {} cannot be mapped", headingType); - } + List sftHeadings = target.getSaftHeadings(); + assertThat(sftHeadings).hasSize(11); + assertArrayEquals(expectedHeadingRefs.toArray(), sftHeadings.toArray()); } @Test - void testExtractAuthorityDtoAdditionalHeadingsWithNullValues() { + void testExtractAuthorityDtoSaftHeadingsWithRelationships() { + final List saftHeadings = getHeadingRefs(); + target.setSaftHeadings(saftHeadings); + final AuthorityDto expectedAuthorityDto = getAuthorityDtoWithSaftTerms(); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(target, source); - assertTrue(source.getBroaderTerm().isEmpty()); - assertTrue(source.getNarrowerTerm().isEmpty()); - assertTrue(source.getEarlierHeading().isEmpty()); - assertTrue(source.getLaterHeading().isEmpty()); - assertTrue(source.getSftTerm().isEmpty()); - assertTrue(source.getSaftTerm().isEmpty()); - } - - @Test - void testExtractAuthorityDtoAdditionalHeadingsWithMixedHeadingTypes() { - List additionalHeadings = new ArrayList<>(); - additionalHeadings.add(new HeadingRef(BROADER_TERM, "broaderTerm")); - additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm1")); - additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm2")); - additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading1")); - additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading2")); - additionalHeadings.add(new HeadingRef(LATER_HEADING, "laterHeading")); - additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm1")); - additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm2")); - additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm1")); - additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm2")); - target.setAdditionalHeadings(additionalHeadings); - - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); - - assertArrayEquals(new String[] {"broaderTerm"}, source.getBroaderTerm().toArray()); - assertArrayEquals(new String[] {"narrowerTerm1", "narrowerTerm2"}, source.getNarrowerTerm().toArray()); - assertArrayEquals(new String[] {"earlierHeading1", "earlierHeading2"}, source.getEarlierHeading().toArray()); - assertArrayEquals(new String[] {"laterHeading"}, source.getLaterHeading().toArray()); - assertArrayEquals(new String[] {"sftTerm1", "sftTerm2"}, source.getSftTerm().toArray()); - assertArrayEquals(new String[] {"saftTerm1", "saftTerm2"}, source.getSaftTerm().toArray()); + assertEquals(expectedAuthorityDto, source); } private static Stream headingTypeAndValueProvider() { @@ -323,15 +247,45 @@ private static Stream headingTypeAndValueProvider() { ); } - private static Stream additionalHeadingTypeAndValuesProvider() { - return Stream.of( - arguments(BROADER_TERM, List.of(TEST_STRING)), - arguments(NARROWER_TERM, List.of(TEST_STRING, TEST_STRING)), - arguments(EARLIER_HEADING, List.of(TEST_STRING)), - arguments(LATER_HEADING, List.of(TEST_STRING, TEST_STRING)), - arguments(SFT_TERM, List.of(TEST_STRING, TEST_STRING)), - arguments(SAFT_TERM, List.of(TEST_STRING, TEST_STRING, TEST_STRING)), - arguments(SAFT_TERM, List.of()) - ); + private static List getHeadingRefs() { + return List.of( + new HeadingRef(PERSONAL_NAME_HEADING, PERSONAL_NAME_HEADING), + new HeadingRef(PERSONAL_NAME_HEADING, "broaderTerm1", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(CORPORATE_NAME_HEADING, CORPORATE_NAME_HEADING), + new HeadingRef(CORPORATE_NAME_HEADING, "broaderTerm2", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(CORPORATE_NAME_HEADING, "laterHeading", Set.of(RelationshipType.LATER_HEADING)), + new HeadingRef(MEETING_NAME_HEADING, MEETING_NAME_HEADING), + new HeadingRef(MEETING_NAME_HEADING, "narrowerTerm", Set.of(RelationshipType.NARROWER_TERM)), + new HeadingRef(MEETING_NAME_HEADING, "narrower-later", Set.of(RelationshipType.NARROWER_TERM, + RelationshipType.LATER_HEADING)), + new HeadingRef(TOPICAL_TERM_HEADING, TOPICAL_TERM_HEADING), + new HeadingRef(TOPICAL_TERM_HEADING, "broaderTerm1", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(TOPICAL_TERM_HEADING, "earlierHeading", Set.of(RelationshipType.EARLIER_HEADING))); + } + + private static AuthorityDto getAuthorityDtoWithSftTerms() { + AuthorityDto authorityDto = new AuthorityDto(); + authorityDto.setSftBroaderTerm(List.of("broaderTerm1", "broaderTerm2")); + authorityDto.setSftNarrowerTerm(List.of("narrowerTerm", "narrower-later")); + authorityDto.setSftEarlierHeading(List.of("earlierHeading")); + authorityDto.setSftLaterHeading(List.of("laterHeading", "narrower-later")); + authorityDto.setSftPersonalName(List.of(PERSONAL_NAME_HEADING, "broaderTerm1")); + authorityDto.setSftCorporateName(List.of(CORPORATE_NAME_HEADING, "broaderTerm2", "laterHeading")); + authorityDto.setSftMeetingName(List.of(MEETING_NAME_HEADING, "narrowerTerm", "narrower-later")); + authorityDto.setSftTopicalTerm(List.of(TOPICAL_TERM_HEADING, "broaderTerm1", "earlierHeading")); + return authorityDto; + } + + private static AuthorityDto getAuthorityDtoWithSaftTerms() { + AuthorityDto authorityDto = new AuthorityDto(); + authorityDto.setSaftBroaderTerm(List.of("broaderTerm1", "broaderTerm2")); + authorityDto.setSaftNarrowerTerm(List.of("narrowerTerm", "narrower-later")); + authorityDto.setSaftEarlierHeading(List.of("earlierHeading")); + authorityDto.setSaftLaterHeading(List.of("laterHeading", "narrower-later")); + authorityDto.setSaftPersonalName(List.of(PERSONAL_NAME_HEADING, "broaderTerm1")); + authorityDto.setSaftCorporateName(List.of(CORPORATE_NAME_HEADING, "broaderTerm2", "laterHeading")); + authorityDto.setSaftMeetingName(List.of(MEETING_NAME_HEADING, "narrowerTerm", "narrower-later")); + authorityDto.setSaftTopicalTerm(List.of(TOPICAL_TERM_HEADING, "broaderTerm1", "earlierHeading")); + return authorityDto; } } diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 79e12863..412a69c3 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,12 +130,6 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); - existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), - new HeadingRef("narrowerTerm", "narrower"), - new HeadingRef("earlierHeading", "earlier"), - new HeadingRef("laterHeading", "later"), - new HeadingRef("sftTerm", "sft"), - new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -151,12 +145,6 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); - modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), - new HeadingRef("narrowerTerm", "narrowerNew"), - new HeadingRef("earlierHeading", "earlierNew"), - new HeadingRef("laterHeading", "laterNew"), - new HeadingRef("sftTerm", "sftNew"), - new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew); From 9db146ed7bb9e4970cd8bf33b3529a130e45084a Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 30 Aug 2024 18:04:19 +0300 Subject: [PATCH 03/12] feature/MODELINKS-248 Cover new fields with integration tests --- .../org/folio/entlinks/controller/AuthorityControllerIT.java | 4 ++++ src/test/java/org/folio/support/TestDataUtils.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java b/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java index 9a1ed8f4..dfaeb961 100644 --- a/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java +++ b/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java @@ -417,6 +417,8 @@ void createAuthority_positive_entityCreatedWithNewId() throws Exception { assertEquals(dto.getIdentifiers(), created.getIdentifiers()); assertEquals(dto.getSftPersonalName(), created.getSftPersonalName()); assertEquals(dto.getSaftPersonalName(), created.getSaftPersonalName()); + assertEquals(dto.getSftNarrowerTerm(), created.getSftNarrowerTerm()); + assertEquals(dto.getSaftBroaderTerm(), created.getSaftBroaderTerm()); } @Test @@ -521,6 +523,8 @@ void updateAuthority_positive_entityUpdated() throws Exception { assertEquals(expected.getSaftPersonalName(), resultDto.getSaftPersonalName()); assertEquals(expected.getSftCorporateName(), resultDto.getSftCorporateName()); assertEquals(expected.getSaftCorporateName(), resultDto.getSaftCorporateName()); + assertEquals(dto.getSftNarrowerTerm(), resultDto.getSftNarrowerTerm()); + assertEquals(dto.getSaftBroaderTerm(), resultDto.getSaftBroaderTerm()); var event = getConsumedEvent(); awaitUntilAsserted(() -> diff --git a/src/test/java/org/folio/support/TestDataUtils.java b/src/test/java/org/folio/support/TestDataUtils.java index 2c29083e..8bfa5a30 100644 --- a/src/test/java/org/folio/support/TestDataUtils.java +++ b/src/test/java/org/folio/support/TestDataUtils.java @@ -412,6 +412,9 @@ public static AuthorityDto authorityDto(int authorityIdNum, int sourceFileIdNum) dto.addSftMeetingNameItem("sftMeetingNameItem2"); dto.addSaftMeetingNameItem("sftMeetingNameItem1"); dto.addSaftMeetingNameItem("sftMeetingNameItem2"); + dto.addSftNarrowerTermItem("sftPersonalName2"); + dto.addSftNarrowerTermItem("sftMeetingNameItem1"); + dto.addSaftBroaderTermItem("saftPersonalName1"); return dto; } From 44117a0f0e54bf8717d7434d8e1e4f8007e8eb47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 07:56:46 +0000 Subject: [PATCH 04/12] fix(deps): bump the production-dependencies group across 1 directory with 6 updates (#321) --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index e4754069..7a916e1d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.2 + 3.3.3 @@ -28,7 +28,7 @@ 8.2.0-SNAPSHOT 4.1.0-SNAPSHOT 2.2.0-SNAPSHOT - 2.27.8 + 2.27.17 1.6.0 0.2.0 2.9.5 @@ -37,8 +37,8 @@ 7.8.0 1.0.1 - 3.4.0 - 3.4.0 + 3.5.0 + 3.5.0 @@ -395,7 +395,7 @@ com.puppycrawl.tools checkstyle - 10.17.0 + 10.18.1 @@ -424,7 +424,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.4.0 + 3.5.0 From 4319dec8c42a97a7fb8c4b34b6c006333fd67e5e Mon Sep 17 00:00:00 2001 From: Viacheslav Kolesnyk <94473337+viacheslavkol@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:42:25 +0200 Subject: [PATCH 05/12] fix(authority): Add parsing errors handling on bulk api (#320) * fix(authority): Add parsing errors handling - Collect parsing errors by authority - Process authorities with no parsing errors - Aggregate and save parsing/processing errors - Implement retries on s3 client requests Closes: MODELINKS-249 --- NEWS.md | 1 + descriptors/ModuleDescriptor-template.json | 10 ++ .../entlinks/config/RemoteStorageConfig.java | 2 + .../service/authority/AuthorityS3Service.java | 97 +++++++++++++------ .../authority/BulkAuthorityS3Client.java | 42 ++++++++ src/main/resources/application.yaml | 2 + .../entlinks/controller/AuthorityBulkIT.java | 27 ++++-- .../authority/AuthorityS3ServiceTest.java | 49 +++++----- .../authority/BulkAuthorityS3ClientTest.java | 59 +++++++++++ .../test-data/authorities/bulkAuthorities | 3 +- 10 files changed, 231 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/folio/entlinks/service/authority/BulkAuthorityS3Client.java create mode 100644 src/test/java/org/folio/entlinks/service/authority/BulkAuthorityS3ClientTest.java diff --git a/NEWS.md b/NEWS.md index dbfca9b3..5f7f5dd6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,7 @@ * Add checking for Authority source file references for member tenant in ECS ([MODELINKS-227](https://issues.folio.org/browse/MODELINKS-227)) * Return only ids in response when idOnly=true ([MODELINKS-237](https://issues.folio.org/browse/MODELINKS-227)) * Fix totalRecords count when idOnly=true ([MODELINKS-239](https://issues.folio.org/browse/MODELINKS-239)) +* Add parsing errors handling on bulk api ([MODELINKS-249](https://issues.folio.org/browse/MODELINKS-249)) ### Tech Dept * Fix issue that causes repeated update of same entity with latest Hibernate versions ([MODELINKS-237](https://issues.folio.org/browse/MODELINKS-227)) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index c20e2561..3274514a 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -906,6 +906,16 @@ "name": "S3_IS_AWS", "value": "true", "description": "Specify if AWS S3 is used as files storage" + }, + { + "name": "S3_RETRY_COUNT", + "value": "3", + "description": "Specify number of retries if S3 client return any kind of error" + }, + { + "name": "S3_RETRY_DELAY_MS", + "value": "500", + "description": "Specify millisecond delay between retries if S3 client return any kind of error" } ] } diff --git a/src/main/java/org/folio/entlinks/config/RemoteStorageConfig.java b/src/main/java/org/folio/entlinks/config/RemoteStorageConfig.java index cd95d9bd..2bdfad33 100644 --- a/src/main/java/org/folio/entlinks/config/RemoteStorageConfig.java +++ b/src/main/java/org/folio/entlinks/config/RemoteStorageConfig.java @@ -24,6 +24,8 @@ public class RemoteStorageConfig { private String accessKey; private String secretKey; private boolean awsSdk; + private Integer retryCount; + private Integer retryDelayMs; @Bean public FolioS3Client remoteFolioS3Client() { diff --git a/src/main/java/org/folio/entlinks/service/authority/AuthorityS3Service.java b/src/main/java/org/folio/entlinks/service/authority/AuthorityS3Service.java index 2c9eb9cb..62b9f0da 100644 --- a/src/main/java/org/folio/entlinks/service/authority/AuthorityS3Service.java +++ b/src/main/java/org/folio/entlinks/service/authority/AuthorityS3Service.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.entlinks.controller.converter.AuthorityMapper; @@ -16,7 +16,6 @@ import org.folio.entlinks.domain.entity.Authority; import org.folio.entlinks.exception.FolioIntegrationException; import org.folio.entlinks.utils.EntitiesErrorFileWriter; -import org.folio.s3.client.FolioS3Client; import org.springframework.stereotype.Service; @Log4j2 @@ -24,25 +23,18 @@ @RequiredArgsConstructor public class AuthorityS3Service { - private final FolioS3Client s3Client; + private final BulkAuthorityS3Client s3Client; private final ObjectMapper objectMapper; private final AuthorityMapper mapper; - public Stream readStream(String remoteFileName) { - log.info("readStream::Reading authorities from the file [filename: {}]", remoteFileName); - var inputStream = s3Client.read(remoteFileName); - return new BufferedReader(new InputStreamReader(inputStream)) - .lines() - .map(this::toAuthorityDto); - } - public int processAuthorities(AuthoritiesBulkContext bulkContext, Consumer> bulkConsumer) { log.info("processAuthorities::Processing bulk authority request [filename: {}]", bulkContext.getInitialFilePath()); AtomicInteger errorCounter = new AtomicInteger(); - var authorities = readStream(bulkContext.getInitialFilePath()) - .map(mapper::toEntity) - .toList(); + var stringAuthorities = s3Client.readFile(bulkContext.getInitialFilePath()); + var invalidFormatAuthorities = new LinkedList>(); + var authorities = parseAuthorities(stringAuthorities, invalidFormatAuthorities); + var failedAuthorities = new LinkedList>(); try { bulkConsumer.accept(authorities); @@ -50,20 +42,17 @@ public int processAuthorities(AuthoritiesBulkContext bulkContext, log.error("processAuthorities::Batch failed: [message: {}]", e.getMessage()); log.debug("processAuthorities::Batch failed", e); - try (var writer = new EntitiesErrorFileWriter(bulkContext.getLocalFailedEntitiesFile(), - bulkContext.getLocalErrorsFile(), objectMapper)) { - for (Authority authority : authorities) { - processSingleAuthority(authority, bulkConsumer, errorCounter, writer); + for (var authority : authorities) { + try { + bulkConsumer.accept(List.of(authority)); + } catch (Exception ex) { + failedAuthorities.add(Map.entry(authority, ex)); } - } catch (Exception ex) { - log.error("processAuthorities::Processing bulk authority request failed.", ex); } } - if (errorCounter.get() != 0) { - s3Client.upload(bulkContext.getLocalFailedEntitiesFilePath(), bulkContext.getFailedEntitiesFilePath()); - s3Client.upload(bulkContext.getLocalErrorsFilePath(), bulkContext.getErrorsFilePath()); - } + processFailures(invalidFormatAuthorities, failedAuthorities, errorCounter, bulkContext); + try { bulkContext.deleteLocalFiles(); } catch (IOException e) { @@ -72,13 +61,59 @@ public int processAuthorities(AuthoritiesBulkContext bulkContext, return errorCounter.get(); } - private void processSingleAuthority(Authority authority, Consumer> bulkConsumer, - AtomicInteger errorCounter, EntitiesErrorFileWriter writer) throws IOException { + private List parseAuthorities(List stringAuthorities, + List> invalidFormatAuthorities) { + var authorities = new LinkedList(); + + for (String stringAuthority : stringAuthorities) { + try { + var authorityDto = toAuthorityDto(stringAuthority); + var authority = mapper.toEntity(authorityDto); + authorities.add(authority); + } catch (Exception ex) { + invalidFormatAuthorities.add(Map.entry(stringAuthority, ex)); + } + } + + return authorities; + } + + private void processFailures(List> invalidFormatAuthorities, + List> failedAuthorities, + AtomicInteger errorCounter, AuthoritiesBulkContext bulkContext) { + + if (invalidFormatAuthorities.isEmpty() && failedAuthorities.isEmpty()) { + return; + } + + log.warn("processFailures:: [file: {}, invalid format count: {}, process failures count: {}]", + bulkContext.getInitialFilePath(), invalidFormatAuthorities.size(), failedAuthorities.size()); + try (var writer = new EntitiesErrorFileWriter(bulkContext.getLocalFailedEntitiesFile(), + bulkContext.getLocalErrorsFile(), objectMapper)) { + for (var authority : invalidFormatAuthorities) { + errorCounter.incrementAndGet(); + writer.write(authority.getKey(), authority.getValue(), + a -> stringAuthorityIdGetter(bulkContext.getInitialFilePath(), a)); + } + for (var authority : failedAuthorities) { + errorCounter.incrementAndGet(); + writer.write(mapper.toDto(authority.getKey()), authority.getValue(), a -> a.getId().toString()); + } + } catch (Exception ex) { + log.error("processFailures::Processing bulk authority request failed.", ex); + } + + s3Client.uploadErrorFiles(bulkContext); + } + + private String stringAuthorityIdGetter(String initialFilePath, String stringAuthority) { try { - bulkConsumer.accept(List.of(authority)); + var authorityJson = objectMapper.readValue(stringAuthority, HashMap.class); + return String.valueOf(authorityJson.get("id")); } catch (Exception ex) { - errorCounter.incrementAndGet(); - writer.write(mapper.toDto(authority), ex, a -> a.getId().toString()); + log.warn("stringAuthorityIdGetter:: unable to get authority id [file: {}, authority string: {}]", + initialFilePath, stringAuthority); + return null; } } diff --git a/src/main/java/org/folio/entlinks/service/authority/BulkAuthorityS3Client.java b/src/main/java/org/folio/entlinks/service/authority/BulkAuthorityS3Client.java new file mode 100644 index 00000000..770556e4 --- /dev/null +++ b/src/main/java/org/folio/entlinks/service/authority/BulkAuthorityS3Client.java @@ -0,0 +1,42 @@ +package org.folio.entlinks.service.authority; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.s3.client.FolioS3Client; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class BulkAuthorityS3Client { + + private final FolioS3Client s3Client; + + @Retryable( + retryFor = Exception.class, + maxAttemptsExpression = "${folio.remote-storage.retryCount}", + backoff = @Backoff(delayExpression = "${folio.remote-storage.retryDelayMs}")) + public List readFile(String remoteFileName) { + log.info("readFile::Reading lines from the file [filename: {}]", remoteFileName); + var inputStream = s3Client.read(remoteFileName); + return new BufferedReader(new InputStreamReader(inputStream)) + .lines() + .toList(); + } + + @Retryable( + retryFor = Exception.class, + maxAttemptsExpression = "${folio.remote-storage.retryCount}", + backoff = @Backoff(delayExpression = "${folio.remote-storage.retryDelayMs}")) + public void uploadErrorFiles(AuthoritiesBulkContext bulkContext) { + log.info("uploadErrorFiles::Uploading [failedEntities: {}, errors: {}]", + bulkContext.getFailedEntitiesFilePath(), bulkContext.getErrorsFilePath()); + s3Client.upload(bulkContext.getLocalFailedEntitiesFilePath(), bulkContext.getFailedEntitiesFilePath()); + s3Client.upload(bulkContext.getLocalErrorsFilePath(), bulkContext.getErrorsFilePath()); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 08695ca2..b72c6f00 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -110,6 +110,8 @@ folio: accessKey: ${S3_ACCESS_KEY_ID:} secretKey: ${S3_SECRET_ACCESS_KEY:} awsSdk: ${S3_IS_AWS:false} + retryCount: ${S3_RETRY_COUNT:3} + retryDelayMs: ${S3_RETRY_DELAY_MS:500} retry: enabled: true diff --git a/src/test/java/org/folio/entlinks/controller/AuthorityBulkIT.java b/src/test/java/org/folio/entlinks/controller/AuthorityBulkIT.java index 116da0c4..a003a8a7 100644 --- a/src/test/java/org/folio/entlinks/controller/AuthorityBulkIT.java +++ b/src/test/java/org/folio/entlinks/controller/AuthorityBulkIT.java @@ -6,11 +6,14 @@ import static org.folio.support.base.TestConstants.authorityEndpoint; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import org.folio.entlinks.domain.dto.AuthorityBulkRequest; import org.folio.s3.client.FolioS3Client; @@ -22,8 +25,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.core.io.ResourceLoader; +import org.springframework.retry.annotation.EnableRetry; +@EnableRetry @IntegrationTest @DatabaseCleanup(tables = { DatabaseHelper.AUTHORITY_SOURCE_FILE_CODE_TABLE, @@ -32,11 +38,11 @@ DatabaseHelper.AUTHORITY_SOURCE_FILE_TABLE}) class AuthorityBulkIT extends IntegrationTestBase { - private @Autowired FolioS3Client s3Client; + private @SpyBean FolioS3Client s3Client; private @Autowired ResourceLoader loader; @BeforeAll - static void prepare() throws IOException { + static void prepare() { setUpTenant(true); } @@ -47,9 +53,14 @@ void createAuthority_positive_entityCreatedWithProvidedId() throws Exception { var filename = s3Client.write("parentLocation/filePath/fileName", resource.getInputStream()); var dto = new AuthorityBulkRequest(filename); + //trigger spring retry once, then - call real methods + doThrow(IllegalStateException.class) + .doCallRealMethod() + .when(s3Client).upload(any(), any()); + tryPost(authorityEndpoint() + "/bulk", dto) .andExpect(status().isOk()) - .andExpect(jsonPath("$.errorsNumber", is(1))); + .andExpect(jsonPath("$.errorsNumber", is(2))); assumeTrue(databaseHelper.countRows(AUTHORITY_TABLE, TENANT_ID) == 1); var list = s3Client.list("parentLocation/filePath/"); @@ -62,8 +73,12 @@ void createAuthority_positive_entityCreatedWithProvidedId() throws Exception { .lines() .toList(); assertThat(errors) - .hasSize(1) - .allMatch(s -> s.contains("constraint [authority_storage_source_file_id_foreign_key]")); + .hasSize(2) + .anyMatch(s -> s.contains("constraint [authority_storage_source_file_id_foreign_key]")) + .anyMatch(s -> s.contains("Unexpected json parsing exception")); //invalid UUID for noteTypeId + + //1'st time with exception triggering spring retry, then - 2 regular calls + verify(s3Client, times(3)).upload(any(), any()); } } diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityS3ServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityS3ServiceTest.java index 82b50e73..320894c0 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityS3ServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityS3ServiceTest.java @@ -4,20 +4,18 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; import java.util.UUID; import java.util.function.Consumer; -import java.util.stream.Stream; import org.folio.entlinks.controller.converter.AuthorityMapperImpl; -import org.folio.entlinks.domain.dto.AuthorityDto; import org.folio.entlinks.domain.entity.Authority; -import org.folio.s3.client.FolioS3Client; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +29,7 @@ class AuthorityS3ServiceTest { private static final String AUTHORITY_UUID = "58949d4b-2da2-43ce-b12b-319dd22f5990"; @Mock - private FolioS3Client s3Client; + private BulkAuthorityS3Client s3Client; @Mock private AuthorityMapperImpl mapper; @Mock @@ -44,44 +42,49 @@ void setUp() { } @Test - void readStream_ReturnsStreamOfAuthorityDto() { + void processAuthorities_SuccessfulProcessing_NoErrorReturned() throws IOException { // Arrange - String remoteFileName = "test-file"; - String authorityJson = "{\"id\": \"" + AUTHORITY_UUID + "\", \"personalName\": \"Test Authority\"}"; - ByteArrayInputStream inputStream = new ByteArrayInputStream(authorityJson.getBytes()); - when(s3Client.read(remoteFileName)).thenReturn(inputStream); + AuthoritiesBulkContext bulkContext = mock(AuthoritiesBulkContext.class); + var authorityJson = "{\"id\": \"" + AUTHORITY_UUID + "\", \"personalName\": \"Test Authority\"}"; + when(s3Client.readFile(any())).thenReturn(List.of(authorityJson)); + when(mapper.toEntity(any())).thenCallRealMethod(); // Act - Stream resultStream = authorityS3Service.readStream(remoteFileName); + int errorCount = authorityS3Service.processAuthorities(bulkContext, bulkConsumer); // Assert - List resultList = resultStream.toList(); - assertEquals(1, resultList.size()); - assertEquals(UUID.fromString(AUTHORITY_UUID), resultList.get(0).getId()); - assertEquals("Test Authority", resultList.get(0).getPersonalName()); + assertEquals(0, errorCount); + var testAuthority = new Authority(); + testAuthority.setId(UUID.fromString(AUTHORITY_UUID)); + testAuthority.setHeading("Test Authority"); + testAuthority.setHeadingType("personalName"); + verify(bulkConsumer).accept(List.of(testAuthority)); + verify(bulkContext).deleteLocalFiles(); + verify(s3Client, never()).uploadErrorFiles(any()); } @Test - void processAuthorities_SuccessfulProcessing_NoErrorReturned() throws IOException { + void processAuthorities_multipleAuthorities_invalidId() throws IOException { // Arrange - AuthoritiesBulkContext bulkContext = mock(AuthoritiesBulkContext.class); - String authorityJson = "{\"id\": \"" + AUTHORITY_UUID + "\", \"personalName\": \"Test Authority\"}"; - ByteArrayInputStream inputStream = new ByteArrayInputStream(authorityJson.getBytes()); - when(s3Client.read(any())).thenReturn(inputStream); + var bulkContext = spy(new AuthoritiesBulkContext("test")); + var authoritiesJson = List.of("{\"id\": \"" + AUTHORITY_UUID + "\", \"personalName\": \"Test Authority 1\"}", + "{\"id\": \"invalidId\", \"personalName\": \"Test Authority 2\"}"); + when(s3Client.readFile(any())).thenReturn(authoritiesJson); when(mapper.toEntity(any())).thenCallRealMethod(); // Act int errorCount = authorityS3Service.processAuthorities(bulkContext, bulkConsumer); // Assert - assertEquals(0, errorCount); + assertEquals(1, errorCount); var testAuthority = new Authority(); testAuthority.setId(UUID.fromString(AUTHORITY_UUID)); - testAuthority.setHeading("Test Authority"); + testAuthority.setHeading("Test Authority 1"); testAuthority.setHeadingType("personalName"); verify(bulkConsumer).accept(List.of(testAuthority)); + verifyNoMoreInteractions(bulkConsumer); verify(bulkContext).deleteLocalFiles(); - verify(s3Client, never()).upload(any(), any()); + verify(s3Client).uploadErrorFiles(any()); } } diff --git a/src/test/java/org/folio/entlinks/service/authority/BulkAuthorityS3ClientTest.java b/src/test/java/org/folio/entlinks/service/authority/BulkAuthorityS3ClientTest.java new file mode 100644 index 00000000..6ff9631d --- /dev/null +++ b/src/test/java/org/folio/entlinks/service/authority/BulkAuthorityS3ClientTest.java @@ -0,0 +1,59 @@ +package org.folio.entlinks.service.authority; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.folio.s3.client.FolioS3Client; +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 BulkAuthorityS3ClientTest { + + private static final String AUTHORITY_UUID = "58949d4b-2da2-43ce-b12b-319dd22f5990"; + @Mock + private FolioS3Client s3Client; + @InjectMocks + private BulkAuthorityS3Client client; + + @Test + void readFile_ReturnsListOfStringAuthority() { + // Arrange + var remoteFileName = "test-file"; + var authorityJson = "{\"id\": \"" + AUTHORITY_UUID + "\", \"personalName\": \"Test Authority\"}"; + var inputStream = new ByteArrayInputStream(authorityJson.getBytes()); + when(s3Client.read(remoteFileName)).thenReturn(inputStream); + + // Act + var resultList = client.readFile(remoteFileName); + + // Assert + assertEquals(1, resultList.size()); + var stringAuthority = resultList.get(0); + assertThat(stringAuthority).contains(AUTHORITY_UUID, "Test Authority"); + } + + @Test + void uploadErrorFiles_uploads() throws IOException { + // Arrange + var remoteFileName = "test-file"; + var bulkContext = new AuthoritiesBulkContext(remoteFileName); + + // Act + client.uploadErrorFiles(bulkContext); + + // Assert + verify(s3Client).upload(bulkContext.getLocalFailedEntitiesFilePath(), bulkContext.getFailedEntitiesFilePath()); + verify(s3Client).upload(bulkContext.getLocalErrorsFilePath(), bulkContext.getErrorsFilePath()); + } + +} diff --git a/src/test/resources/test-data/authorities/bulkAuthorities b/src/test/resources/test-data/authorities/bulkAuthorities index ef071dfd..ff98d43b 100644 --- a/src/test/resources/test-data/authorities/bulkAuthorities +++ b/src/test/resources/test-data/authorities/bulkAuthorities @@ -1,2 +1,3 @@ {"id":"64d9dcdf-fb7c-4f12-a4b5-1feeb7adc034","_version":0,"source":"MARC","personalName":"Chin, Staceyann with na as prefix in 010","subjectHeadings":"a","identifiers":[{"value":"7613508","identifierTypeId":"5d164f4b-0b15-4e42-ae75-cfcf85318ad9"},{"value":"na2008052404","identifierTypeId":"c858e4f2-2b6b-4385-842b-60732ee14abb"},{"value":"(OCoLC)oca07838205","identifierTypeId":"7e591197-f335-4afb-bc6d-a6d76ca3bace"}],"notes":[],"sourceFileId":"cb58492d-018e-442d-9ce3-35aabfc524aa","naturalId":"na2008052404","metadata":{"createdDate":"2024-01-22T13:15:34.282513Z","createdByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0","updatedDate":"2024-01-22T13:15:34.282513Z","updatedByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0"}} -{"id":"f2513c06-c8d5-4512-b668-6b13e2542c7c","_version":1,"source":"MARC","personalName":"Kerouac, Jack, 1922-1969","sftPersonalName":["Kerouac, John, 1922-1969","Kérouac, Jean Louis Lebris de, 1922-1969","Chia-lo-kʻo, Chieh-kʻo, 1922-1969","Keruak, Dz︠h︡ek, 1922-1969","Ḳeruʼaḳ, G'eḳ, 1922-1969","קרואק, ג׳ק, 1922־1969"],"subjectHeadings":"a","identifiers":[{"value":"971255","identifierTypeId":"5d164f4b-0b15-4e42-ae75-cfcf85318ad9"},{"value":"n 80036674","identifierTypeId":"c858e4f2-2b6b-4385-842b-60732ee14abb"},{"value":"(OCoLC)oca00418812","identifierTypeId":"7e591197-f335-4afb-bc6d-a6d76ca3bace"}],"notes":[{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Machine-derived non-Latin script reference project."},{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Non-Latin script reference not evaluated."},{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Substitute"}],"sourceFileId":"af045f2f-e851-4613-984c-4bc13430454a","naturalId":"n80036674","metadata":{"createdDate":"2024-01-11T10:03:43.746934Z","createdByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0","updatedDate":"2024-01-16T17:38:09.573278Z","updatedByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0"}} \ No newline at end of file +{"id":"f2513c06-c8d5-4512-b668-6b13e2542c7c","_version":1,"source":"MARC","personalName":"Kerouac, Jack, 1922-1969","sftPersonalName":["Kerouac, John, 1922-1969","Kérouac, Jean Louis Lebris de, 1922-1969","Chia-lo-kʻo, Chieh-kʻo, 1922-1969","Keruak, Dz︠h︡ek, 1922-1969","Ḳeruʼaḳ, G'eḳ, 1922-1969","קרואק, ג׳ק, 1922־1969"],"subjectHeadings":"a","identifiers":[{"value":"971255","identifierTypeId":"5d164f4b-0b15-4e42-ae75-cfcf85318ad9"},{"value":"n 80036674","identifierTypeId":"c858e4f2-2b6b-4385-842b-60732ee14abb"},{"value":"(OCoLC)oca00418812","identifierTypeId":"7e591197-f335-4afb-bc6d-a6d76ca3bace"}],"notes":[{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Machine-derived non-Latin script reference project."},{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Non-Latin script reference not evaluated."},{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147","note":"Substitute"}],"sourceFileId":"af045f2f-e851-4613-984c-4bc13430454a","naturalId":"n80036674","metadata":{"createdDate":"2024-01-11T10:03:43.746934Z","createdByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0","updatedDate":"2024-01-16T17:38:09.573278Z","updatedByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0"}} +{"id":"64d9dcdf-fb7c-4f12-a4b5-1feeb7adc035","_version":0,"source":"MARC","personalName":"Chin, Staceyannn with na as prefix in 010","subjectHeadings":"a","identifiers":[{"value":"7613508","identifierTypeId":"5d164f4b-0b15-4e42-ae75-cfcf85318ad9"},{"value":"na2008052404","identifierTypeId":"c858e4f2-2b6b-4385-842b-60732ee14abb"},{"value":"(OCoLC)oca07838205","identifierTypeId":"7e591197-f335-4afb-bc6d-a6d76ca3bace"}],"notes":[{"noteTypeId":"76c74801-afec-45a0-aad7-3ff23591e147 76c74801-afec-45a0-aad7-3ff23591e147","note":"Machine-derived non-Latin script reference project."}],"sourceFileId":"cb58492d-018e-442d-9ce3-35aabfc524aa","naturalId":"na2008052404","metadata":{"createdDate":"2024-01-22T13:15:34.282513Z","createdByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0","updatedDate":"2024-01-22T13:15:34.282513Z","updatedByUserId":"d4f5d03f-6b96-4b82-be5d-0d018f222aa0"}} \ No newline at end of file From 7b5f55aa6236d1a8e2cb8faa0a3a5679ffe99bd5 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 6 Sep 2024 07:36:38 +0300 Subject: [PATCH 06/12] feature/MODELINKS-248 Add description to authorityStatsDto schema --- .../schemas/authority/control/authorityStatsDto.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json b/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json index 9842e9da..bbf0cfc8 100644 --- a/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json +++ b/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json @@ -14,7 +14,8 @@ "description": "Authority ID" }, "action": { - "$ref": "linkAction.json" + "$ref": "linkAction.json", + "description": "Type of change" }, "naturalIdOld": { "type": "string", @@ -61,7 +62,8 @@ "description": "Amount of linked bib fields that was failed to update during authority update" }, "metadata": { - "$ref": "authorityControlMetadata.json" + "$ref": "authorityControlMetadata.json", + "description": "Meta data for authority control" } } } From 0ebbb9c8a103351ed3f18fed6cb3e46d48f94c91 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Thu, 22 Aug 2024 13:03:48 +0300 Subject: [PATCH 07/12] feature/MODELINKS-248 Extend-authorities --- NEWS.md | 1 + descriptors/ModuleDescriptor-template.json | 2 +- .../controller/converter/AuthorityMapper.java | 2 + .../converter/AuthorityUtilityMapper.java | 50 +++++++ .../entlinks/domain/entity/AuthorityBase.java | 8 + .../domain/entity/AuthorityConstants.java | 12 ++ .../db/changelog/changelog-master.xml | 2 + ...additional-fields-to-authority-archive.xml | 19 +++ .../add-additional-fields-to-authority.xml | 19 +++ .../authority-storage/authorityDto.yaml | 30 ++++ .../converter/AuthorityUtilityMapperTest.java | 138 ++++++++++++++++++ .../authority/AuthorityServiceTest.java | 12 ++ 12 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/NEWS.md b/NEWS.md index 5f7f5dd6..ebd041f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -67,6 +67,7 @@ * Fix authority record update and `updatedByUserId` field assignment ([MODELINKS-219](https://issues.folio.org/browse/MODELINKS-219)) * Fix saving of Authority file with empty Base URL when another Authority file with empty Base URL already exists ([MODELINKS-216](https://issues.folio.org/browse/MODELINKS-216)) * Fix handling of authority heading type change update event ([MODELINKS-242](https://issues.folio.org/browse/MODELINKS-242)) +* Extend authorities with additional fields for Advanced References Classification ([MODELINKS-248](https://issues.folio.org/browse/MODELINKS-248)) ### Tech Dept * Create custom Mockito verifies for Hibernate entities ([MODELINKS-209](https://issues.folio.org/browse/MODELINKS-209)) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 3274514a..250d085d 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -167,7 +167,7 @@ }, { "id": "authority-storage", - "version": "2.1", + "version": "2.2", "handlers": [ { "methods": [ diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 5c4c49f5..21aeb963 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,6 +93,7 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -100,6 +101,7 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java index 3addfb2c..7a3522bd 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java @@ -2,14 +2,20 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; @@ -148,6 +154,29 @@ public static void extractAuthoritySaftHeadings(AuthorityDto source, AuthorityBa target.setSaftHeadings(saftHeadings); } + public static void extractAuthorityAdditionalHeadings(AuthorityDto source, AuthorityBase target) { + List additionalHeadings = new ArrayList<>(); + if (isNotEmpty(source.getBroaderTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getBroaderTerm(), BROADER_TERM)); + } + if (isNotEmpty(source.getNarrowerTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getNarrowerTerm(), NARROWER_TERM)); + } + if (isNotEmpty(source.getEarlierHeading())) { + additionalHeadings.addAll(asSftHeadings(source.getEarlierHeading(), EARLIER_HEADING)); + } + if (isNotEmpty(source.getLaterHeading())) { + additionalHeadings.addAll(asSftHeadings(source.getLaterHeading(), LATER_HEADING)); + } + if (isNotEmpty(source.getSftTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getSftTerm(), SFT_TERM)); + } + if (isNotEmpty(source.getSaftTerm())) { + additionalHeadings.addAll(asSftHeadings(source.getSaftTerm(), SAFT_TERM)); + } + target.setAdditionalHeadings(additionalHeadings); + } + public static void extractAuthorityDtoHeadingValue(AuthorityBase source, AuthorityDto target) { if (source.getHeadingType() == null || source.getHeading() == null) { return; @@ -181,6 +210,12 @@ public static void extractAuthorityDtoSaftHeadings(AuthorityBase source, Authori source.getSaftHeadings().forEach(headingRef -> extractAuthorityDtoSaftHeading(headingRef, target)); } + public static void extractAuthorityDtoAdditionalHeadings(AuthorityBase source, AuthorityDto target) { + if (isNotEmpty(source.getAdditionalHeadings())) { + source.getAdditionalHeadings().forEach(headingRef -> extractAuthorityDtoAdditionalHeading(headingRef, target)); + } + } + private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto target) { if (headingRef == null || headingRef.getHeadingType() == null) { return; @@ -224,4 +259,19 @@ private static List asSftHeadings(List headingValues, String .map(headingValue -> new HeadingRef(headingType, headingValue)) .toList(); } + + private void extractAuthorityDtoAdditionalHeading(HeadingRef headingRef, AuthorityDto target) { + if (headingRef == null || headingRef.getHeadingType() == null) { + return; + } + switch (headingRef.getHeadingType()) { + case BROADER_TERM -> target.addBroaderTermItem(headingRef.getHeading()); + case NARROWER_TERM -> target.addNarrowerTermItem(headingRef.getHeading()); + case EARLIER_HEADING -> target.addEarlierHeadingItem(headingRef.getHeading()); + case LATER_HEADING -> target.addLaterHeadingItem(headingRef.getHeading()); + case SAFT_TERM -> target.addSaftTermItem(headingRef.getHeading()); + case SFT_TERM -> target.addSftTermItem(headingRef.getHeading()); + default -> log.warn("Invalid additional heading type - {} cannot be mapped", headingRef.getHeadingType()); + } + } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index dee2764e..2d7b0a9f 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,6 +42,7 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; + private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -90,6 +91,10 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; + @Column(name = ADDITIONAL_HEADINGS_COLUMN) + @JdbcTypeCode(SqlTypes.JSON) + private List additionalHeadings; + public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -116,6 +121,9 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; + this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() + .map(HeadingRef::new) + .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index 337529a3..feb80395 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,4 +24,16 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; + + public static final String BROADER_TERM = "broaderTerm"; + + public static final String NARROWER_TERM = "narrowerTerm"; + + public static final String EARLIER_HEADING = "earlierHeading"; + + public static final String LATER_HEADING = "laterHeading"; + + public static final String SFT_TERM = "sftTerm"; + + public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 906c7bf5..a4417124 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,4 +23,6 @@ + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml new file mode 100644 index 00000000..404ed022 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority_archive table + + + + + + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml new file mode 100644 index 00000000..80db8253 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority table + + + + + + + diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index c3c3a6ee..aeb4a103 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -163,6 +163,36 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' + broaderTerm: + type: array + description: Terms that represents broader, more general concepts related to the authority record + items: + type: string + narrowerTerm: + type: array + description: Terms that represents narrower, more specific concepts derived from the authority record + items: + type: string + earlierHeading: + type: array + description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + laterHeading: + type: array + description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string + sftTerm: + type: array + description: See from tracing term + items: + type: string + saftTerm: + type: array + description: See also from tracing term + items: + type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java index fd7d5ab4..badc1409 100644 --- a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java +++ b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java @@ -2,17 +2,25 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; +import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; +import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import static org.folio.support.base.TestConstants.TEST_STRING; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -24,6 +32,7 @@ import org.folio.entlinks.domain.entity.Authority; import org.folio.entlinks.domain.entity.HeadingRef; 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.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -181,6 +190,124 @@ void testExtractAuthorityDtoHeadingValue(String headingType, String headingValue } } + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityAdditionalHeadingsWithNonNullValues(String propertyType, List propertyValues) { + switch (propertyType) { + case BROADER_TERM -> source.setBroaderTerm(propertyValues); + case NARROWER_TERM -> source.setNarrowerTerm(propertyValues); + case EARLIER_HEADING -> source.setEarlierHeading(propertyValues); + case LATER_HEADING -> source.setLaterHeading(propertyValues); + case SAFT_TERM -> source.setSaftTerm(propertyValues); + case SFT_TERM -> source.setSftTerm(propertyValues); + default -> fail("Invalid heading type - {} cannot be mapped", propertyType); + } + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + List additionalHeadings = target.getAdditionalHeadings(); + String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); + assertThat(additionalHeadings).hasSize(propertyValues.size()); + additionalHeadings.forEach(a -> assertEquals(propertyType, a.getHeadingType())); + assertArrayEquals(propertyValues.toArray(), targetHeadingValues); + } + + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityAdditionalHeadingsWithNullValues(String propertyType, List propertyValues) { + switch (propertyType) { + case BROADER_TERM -> source.setBroaderTerm(null); + case NARROWER_TERM -> source.setNarrowerTerm(null); + case EARLIER_HEADING -> source.setEarlierHeading(null); + case LATER_HEADING -> source.setLaterHeading(null); + case SAFT_TERM -> source.setSaftTerm(null); + case SFT_TERM -> source.setSftTerm(null); + default -> fail("Invalid heading type - {} cannot be mapped", propertyType); + } + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + assertThat(target.getAdditionalHeadings()).isEmpty(); + } + + @Test + void testExtractAuthorityAdditionalHeadingsWithMixedHeadingTypes() { + source.setBroaderTerm(List.of("boarderTerm1", "boarderTerm2")); + source.setNarrowerTerm(List.of("narrowerTerm")); + source.setEarlierHeading(List.of("earlierHeading")); + source.setLaterHeading(List.of("laterHeading")); + source.setSftTerm(List.of("sftTerm1", "sftTerm2", "sftTerm3")); + source.setSaftTerm(List.of("saftTerm1", "saftTerm2")); + + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + + List additionalHeadings = target.getAdditionalHeadings(); + String[] targetHeadingTypes = additionalHeadings.stream().map(HeadingRef::getHeadingType).toArray(String[]::new); + String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); + assertThat(additionalHeadings).hasSize(10); + assertArrayEquals(new String[]{BROADER_TERM, BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING, SFT_TERM, + SFT_TERM, SFT_TERM, SAFT_TERM, SAFT_TERM}, targetHeadingTypes); + assertArrayEquals(new String[]{"boarderTerm1", "boarderTerm2", "narrowerTerm", "earlierHeading", "laterHeading", + "sftTerm1", "sftTerm2", "sftTerm3", "saftTerm1", "saftTerm2"}, targetHeadingValues); + } + + @ParameterizedTest + @MethodSource("additionalHeadingTypeAndValuesProvider") + void testExtractAuthorityDtoAdditionalHeadingsWithNonNullValues(String headingType, List headingValues) { + List additionalHeadings = headingValues.stream().map(hv -> new HeadingRef(headingType, hv)).toList(); + target.setAdditionalHeadings(additionalHeadings); + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + switch (headingType) { + case BROADER_TERM -> assertArrayEquals(source.getBroaderTerm().toArray(), headingValues.toArray()); + case NARROWER_TERM -> assertArrayEquals(source.getNarrowerTerm().toArray(), headingValues.toArray()); + case EARLIER_HEADING -> assertArrayEquals(source.getEarlierHeading().toArray(), headingValues.toArray()); + case LATER_HEADING -> assertArrayEquals(source.getLaterHeading().toArray(), headingValues.toArray()); + case SFT_TERM -> assertArrayEquals(source.getSftTerm().toArray(), headingValues.toArray()); + case SAFT_TERM -> assertArrayEquals(source.getSaftTerm().toArray(), headingValues.toArray()); + default -> fail("Invalid saft heading type - {} cannot be mapped", headingType); + } + } + + @Test + void testExtractAuthorityDtoAdditionalHeadingsWithNullValues() { + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + assertTrue(source.getBroaderTerm().isEmpty()); + assertTrue(source.getNarrowerTerm().isEmpty()); + assertTrue(source.getEarlierHeading().isEmpty()); + assertTrue(source.getLaterHeading().isEmpty()); + assertTrue(source.getSftTerm().isEmpty()); + assertTrue(source.getSaftTerm().isEmpty()); + } + + @Test + void testExtractAuthorityDtoAdditionalHeadingsWithMixedHeadingTypes() { + List additionalHeadings = new ArrayList<>(); + additionalHeadings.add(new HeadingRef(BROADER_TERM, "broaderTerm")); + additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm1")); + additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm2")); + additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading1")); + additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading2")); + additionalHeadings.add(new HeadingRef(LATER_HEADING, "laterHeading")); + additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm1")); + additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm2")); + additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm1")); + additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm2")); + target.setAdditionalHeadings(additionalHeadings); + + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + + assertArrayEquals(new String[] {"broaderTerm"}, source.getBroaderTerm().toArray()); + assertArrayEquals(new String[] {"narrowerTerm1", "narrowerTerm2"}, source.getNarrowerTerm().toArray()); + assertArrayEquals(new String[] {"earlierHeading1", "earlierHeading2"}, source.getEarlierHeading().toArray()); + assertArrayEquals(new String[] {"laterHeading"}, source.getLaterHeading().toArray()); + assertArrayEquals(new String[] {"sftTerm1", "sftTerm2"}, source.getSftTerm().toArray()); + assertArrayEquals(new String[] {"saftTerm1", "saftTerm2"}, source.getSaftTerm().toArray()); + } + private static Stream headingTypeAndValueProvider() { return Stream.of( arguments(PERSONAL_NAME_HEADING, TEST_STRING), @@ -196,4 +323,15 @@ private static Stream headingTypeAndValueProvider() { ); } + private static Stream additionalHeadingTypeAndValuesProvider() { + return Stream.of( + arguments(BROADER_TERM, List.of(TEST_STRING)), + arguments(NARROWER_TERM, List.of(TEST_STRING, TEST_STRING)), + arguments(EARLIER_HEADING, List.of(TEST_STRING)), + arguments(LATER_HEADING, List.of(TEST_STRING, TEST_STRING)), + arguments(SFT_TERM, List.of(TEST_STRING, TEST_STRING)), + arguments(SAFT_TERM, List.of(TEST_STRING, TEST_STRING, TEST_STRING)), + arguments(SAFT_TERM, List.of()) + ); + } } diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 412a69c3..79e12863 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,6 +130,12 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); + existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), + new HeadingRef("narrowerTerm", "narrower"), + new HeadingRef("earlierHeading", "earlier"), + new HeadingRef("laterHeading", "later"), + new HeadingRef("sftTerm", "sft"), + new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -145,6 +151,12 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); + modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), + new HeadingRef("narrowerTerm", "narrowerNew"), + new HeadingRef("earlierHeading", "earlierNew"), + new HeadingRef("laterHeading", "laterNew"), + new HeadingRef("sftTerm", "sftNew"), + new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew); From 2b3b5c5e65081857d57e6b00b658cc3f6b5e6f21 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 30 Aug 2024 16:05:47 +0300 Subject: [PATCH 08/12] feature/MODELINKS-248 split relationship fields to sft and saft --- .../controller/converter/AuthorityMapper.java | 2 - .../converter/AuthorityUtilityMapper.java | 116 +++++++---- .../entlinks/domain/entity/AuthorityBase.java | 8 - .../domain/entity/AuthorityConstants.java | 12 -- .../entlinks/domain/entity/HeadingRef.java | 11 ++ .../domain/entity/RelationshipType.java | 6 + .../db/changelog/changelog-master.xml | 2 - ...additional-fields-to-authority-archive.xml | 19 -- .../add-additional-fields-to-authority.xml | 19 -- .../authority-storage/authorityDto.yaml | 70 ++++--- .../converter/AuthorityUtilityMapperTest.java | 186 +++++++----------- .../authority/AuthorityServiceTest.java | 12 -- 12 files changed, 200 insertions(+), 263 deletions(-) create mode 100644 src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 21aeb963..5c4c49f5 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,7 +93,6 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -101,7 +100,6 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java index 7a3522bd..18621c80 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapper.java @@ -2,31 +2,28 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; import org.folio.entlinks.domain.dto.AuthorityDto; import org.folio.entlinks.domain.entity.AuthorityBase; import org.folio.entlinks.domain.entity.HeadingRef; +import org.folio.entlinks.domain.entity.RelationshipType; @UtilityClass @Log4j2 @@ -116,6 +113,7 @@ public static void extractAuthoritySftHeadings(AuthorityDto source, AuthorityBas if (isNotEmpty(source.getSftGenreTerm())) { sftHeadings.addAll(asSftHeadings(source.getSftGenreTerm(), GENRE_TERM_HEADING)); } + addRelationshipsToSftHeadings(source, sftHeadings); target.setSftHeadings(sftHeadings); } @@ -151,30 +149,34 @@ public static void extractAuthoritySaftHeadings(AuthorityDto source, AuthorityBa if (isNotEmpty(source.getSaftGenreTerm())) { saftHeadings.addAll(asSftHeadings(source.getSaftGenreTerm(), GENRE_TERM_HEADING)); } + addRelationshipsToSaftHeadings(source, saftHeadings); target.setSaftHeadings(saftHeadings); } - public static void extractAuthorityAdditionalHeadings(AuthorityDto source, AuthorityBase target) { - List additionalHeadings = new ArrayList<>(); - if (isNotEmpty(source.getBroaderTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getBroaderTerm(), BROADER_TERM)); - } - if (isNotEmpty(source.getNarrowerTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getNarrowerTerm(), NARROWER_TERM)); - } - if (isNotEmpty(source.getEarlierHeading())) { - additionalHeadings.addAll(asSftHeadings(source.getEarlierHeading(), EARLIER_HEADING)); - } - if (isNotEmpty(source.getLaterHeading())) { - additionalHeadings.addAll(asSftHeadings(source.getLaterHeading(), LATER_HEADING)); - } - if (isNotEmpty(source.getSftTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getSftTerm(), SFT_TERM)); - } - if (isNotEmpty(source.getSaftTerm())) { - additionalHeadings.addAll(asSftHeadings(source.getSaftTerm(), SAFT_TERM)); + private static void addRelationshipsToSftHeadings(final AuthorityDto source, final List headingRefs) { + processRelationshipHeadings(source.getSftBroaderTerm(), headingRefs, RelationshipType.BROADER_TERM); + processRelationshipHeadings(source.getSftNarrowerTerm(), headingRefs, RelationshipType.NARROWER_TERM); + processRelationshipHeadings(source.getSftEarlierHeading(), headingRefs, RelationshipType.EARLIER_HEADING); + processRelationshipHeadings(source.getSftLaterHeading(), headingRefs, RelationshipType.LATER_HEADING); + } + + private static void addRelationshipsToSaftHeadings(final AuthorityDto source, final List headingRefs) { + processRelationshipHeadings(source.getSaftBroaderTerm(), headingRefs, RelationshipType.BROADER_TERM); + processRelationshipHeadings(source.getSaftNarrowerTerm(), headingRefs, RelationshipType.NARROWER_TERM); + processRelationshipHeadings(source.getSaftEarlierHeading(), headingRefs, RelationshipType.EARLIER_HEADING); + processRelationshipHeadings(source.getSaftLaterHeading(), headingRefs, RelationshipType.LATER_HEADING); + } + + private static void processRelationshipHeadings(List relationshipHeadings, final List headingRefs, + final RelationshipType relationshipType) { + if (isNotEmpty(relationshipHeadings)) { + headingRefs.forEach(headingRef -> { + if (relationshipHeadings.contains(headingRef.getHeading())) { + Set relationshipTypeSet = getOrCreateRelationshipTypeSet(headingRef); + relationshipTypeSet.add(relationshipType); + } + }); } - target.setAdditionalHeadings(additionalHeadings); } public static void extractAuthorityDtoHeadingValue(AuthorityBase source, AuthorityDto target) { @@ -210,12 +212,6 @@ public static void extractAuthorityDtoSaftHeadings(AuthorityBase source, Authori source.getSaftHeadings().forEach(headingRef -> extractAuthorityDtoSaftHeading(headingRef, target)); } - public static void extractAuthorityDtoAdditionalHeadings(AuthorityBase source, AuthorityDto target) { - if (isNotEmpty(source.getAdditionalHeadings())) { - source.getAdditionalHeadings().forEach(headingRef -> extractAuthorityDtoAdditionalHeading(headingRef, target)); - } - } - private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto target) { if (headingRef == null || headingRef.getHeadingType() == null) { return; @@ -233,6 +229,7 @@ private void extractAuthorityDtoSftHeading(HeadingRef headingRef, AuthorityDto t case GENRE_TERM_HEADING -> target.addSftGenreTermItem(headingRef.getHeading()); default -> log.warn("Invalid sft heading type - {} cannot be mapped", headingRef.getHeadingType()); } + extractSftHeadingsRelationships(headingRef, target); } private void extractAuthorityDtoSaftHeading(HeadingRef headingRef, AuthorityDto target) { @@ -252,6 +249,39 @@ private void extractAuthorityDtoSaftHeading(HeadingRef headingRef, AuthorityDto case GENRE_TERM_HEADING -> target.addSaftGenreTermItem(headingRef.getHeading()); default -> log.warn("Invalid saft heading type - {} cannot be mapped", headingRef.getHeadingType()); } + extractSaftHeadingsRelationships(headingRef, target); + } + + private static void extractSftHeadingsRelationships(HeadingRef headingRef, AuthorityDto target) { + if (isNotEmpty(headingRef.getRelationshipType())) { + headingRef.getRelationshipType().forEach( + relationshipType -> { + switch (relationshipType) { + case BROADER_TERM -> addIfNotExists(target.getSftBroaderTerm(), headingRef.getHeading()); + case NARROWER_TERM -> addIfNotExists(target.getSftNarrowerTerm(), headingRef.getHeading()); + case EARLIER_HEADING -> addIfNotExists(target.getSftEarlierHeading(), headingRef.getHeading()); + case LATER_HEADING -> addIfNotExists(target.getSftLaterHeading(), headingRef.getHeading()); + default -> log.warn("Invalid sft relationship type - {} cannot be mapped", relationshipType); + } + } + ); + } + } + + private static void extractSaftHeadingsRelationships(HeadingRef headingRef, AuthorityDto target) { + if (isNotEmpty(headingRef.getRelationshipType())) { + headingRef.getRelationshipType().forEach( + relationshipType -> { + switch (relationshipType) { + case BROADER_TERM -> addIfNotExists(target.getSaftBroaderTerm(), headingRef.getHeading()); + case NARROWER_TERM -> addIfNotExists(target.getSaftNarrowerTerm(), headingRef.getHeading()); + case EARLIER_HEADING -> addIfNotExists(target.getSaftEarlierHeading(), headingRef.getHeading()); + case LATER_HEADING -> addIfNotExists(target.getSaftLaterHeading(), headingRef.getHeading()); + default -> log.warn("Invalid saft relationship type - {} cannot be mapped", relationshipType); + } + } + ); + } } private static List asSftHeadings(List headingValues, String headingType) { @@ -260,18 +290,18 @@ private static List asSftHeadings(List headingValues, String .toList(); } - private void extractAuthorityDtoAdditionalHeading(HeadingRef headingRef, AuthorityDto target) { - if (headingRef == null || headingRef.getHeadingType() == null) { - return; + private static Set getOrCreateRelationshipTypeSet(HeadingRef heading) { + Set relationshipTypeSet = heading.getRelationshipType(); + if (relationshipTypeSet == null) { + relationshipTypeSet = new HashSet<>(); + heading.setRelationshipType(relationshipTypeSet); } - switch (headingRef.getHeadingType()) { - case BROADER_TERM -> target.addBroaderTermItem(headingRef.getHeading()); - case NARROWER_TERM -> target.addNarrowerTermItem(headingRef.getHeading()); - case EARLIER_HEADING -> target.addEarlierHeadingItem(headingRef.getHeading()); - case LATER_HEADING -> target.addLaterHeadingItem(headingRef.getHeading()); - case SAFT_TERM -> target.addSaftTermItem(headingRef.getHeading()); - case SFT_TERM -> target.addSftTermItem(headingRef.getHeading()); - default -> log.warn("Invalid additional heading type - {} cannot be mapped", headingRef.getHeadingType()); + return relationshipTypeSet; + } + + private static void addIfNotExists(List headings, String heading) { + if (!headings.contains(heading)) { + headings.add(heading); } } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index 2d7b0a9f..dee2764e 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,7 +42,6 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; - private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -91,10 +90,6 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; - @Column(name = ADDITIONAL_HEADINGS_COLUMN) - @JdbcTypeCode(SqlTypes.JSON) - private List additionalHeadings; - public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -121,9 +116,6 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; - this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() - .map(HeadingRef::new) - .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index feb80395..337529a3 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,16 +24,4 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; - - public static final String BROADER_TERM = "broaderTerm"; - - public static final String NARROWER_TERM = "narrowerTerm"; - - public static final String EARLIER_HEADING = "earlierHeading"; - - public static final String LATER_HEADING = "laterHeading"; - - public static final String SFT_TERM = "sftTerm"; - - public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java b/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java index bf9b72a1..f74fc8f6 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java +++ b/src/main/java/org/folio/entlinks/domain/entity/HeadingRef.java @@ -1,6 +1,8 @@ package org.folio.entlinks.domain.entity; +import com.fasterxml.jackson.annotation.JsonInclude; import java.io.Serializable; +import java.util.Set; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -14,14 +16,23 @@ @AllArgsConstructor @ToString @EqualsAndHashCode +@JsonInclude(JsonInclude.Include.NON_NULL) public class HeadingRef implements Serializable { private String headingType; private String heading; + private Set relationshipType; + public HeadingRef(HeadingRef other) { this.heading = other.heading; this.headingType = other.headingType; + this.relationshipType = other.relationshipType; + } + + public HeadingRef(String headingType, String heading) { + this.headingType = headingType; + this.heading = heading; } } diff --git a/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java b/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java new file mode 100644 index 00000000..b830b5b5 --- /dev/null +++ b/src/main/java/org/folio/entlinks/domain/entity/RelationshipType.java @@ -0,0 +1,6 @@ +package org.folio.entlinks.domain.entity; + +public enum RelationshipType { + + BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING +} diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index a4417124..906c7bf5 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,6 +23,4 @@ - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml deleted file mode 100644 index 404ed022..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority_archive table - - - - - - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml deleted file mode 100644 index 80db8253..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority table - - - - - - - diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index aeb4a103..9e67295e 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -110,6 +110,26 @@ properties: description: See from tracing topical term items: type: string + sftBroaderTerm: + type: array + description: See from tracing term that represents broader, more general concepts related to the authority record + items: + type: string + sftNarrowerTerm: + type: array + description: See from tracing term that that represents narrower, more specific concepts derived from the authority record + items: + type: string + sftEarlierHeading: + type: array + description: See from tracing heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + sftLaterHeading: + type: array + description: See from tracing heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string saftTopicalTerm: type: array description: See also from tracing topical term @@ -144,6 +164,26 @@ properties: description: See also from tracing genre/form term items: type: string + saftBroaderTerm: + type: array + description: See also from tracing term that represents broader, more general concepts related to the authority record + items: + type: string + saftNarrowerTerm: + type: array + description: See also from tracing term that that represents narrower, more specific concepts derived from the authority record + items: + type: string + saftEarlierHeading: + type: array + description: See also from tracing heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + saftLaterHeading: + type: array + description: See also from tracing heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string identifiers: type: array description: An extensible set of name-value pairs of identifiers associated with the resource @@ -163,36 +203,6 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' - broaderTerm: - type: array - description: Terms that represents broader, more general concepts related to the authority record - items: - type: string - narrowerTerm: - type: array - description: Terms that represents narrower, more specific concepts derived from the authority record - items: - type: string - earlierHeading: - type: array - description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. - items: - type: string - laterHeading: - type: array - description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. - items: - type: string - sftTerm: - type: array - description: See from tracing term - items: - type: string - saftTerm: - type: array - description: See also from tracing term - items: - type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java index badc1409..29b838f5 100644 --- a/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java +++ b/src/test/java/org/folio/entlinks/controller/converter/AuthorityUtilityMapperTest.java @@ -2,20 +2,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; -import static org.folio.entlinks.domain.entity.AuthorityConstants.BROADER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.CORPORATE_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.EARLIER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GENRE_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.GEOGRAPHIC_NAME_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.LATER_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.MEETING_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.NARROWER_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.PERSONAL_NAME_TITLE_HEADING; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SAFT_TERM; -import static org.folio.entlinks.domain.entity.AuthorityConstants.SFT_TERM; import static org.folio.entlinks.domain.entity.AuthorityConstants.TOPICAL_TERM_HEADING; import static org.folio.entlinks.domain.entity.AuthorityConstants.UNIFORM_TITLE_HEADING; import static org.folio.support.base.TestConstants.TEST_STRING; @@ -27,10 +21,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.folio.entlinks.domain.dto.AuthorityDto; import org.folio.entlinks.domain.entity.Authority; import org.folio.entlinks.domain.entity.HeadingRef; +import org.folio.entlinks.domain.entity.RelationshipType; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -190,122 +186,50 @@ void testExtractAuthorityDtoHeadingValue(String headingType, String headingValue } } - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityAdditionalHeadingsWithNonNullValues(String propertyType, List propertyValues) { - switch (propertyType) { - case BROADER_TERM -> source.setBroaderTerm(propertyValues); - case NARROWER_TERM -> source.setNarrowerTerm(propertyValues); - case EARLIER_HEADING -> source.setEarlierHeading(propertyValues); - case LATER_HEADING -> source.setLaterHeading(propertyValues); - case SAFT_TERM -> source.setSaftTerm(propertyValues); - case SFT_TERM -> source.setSftTerm(propertyValues); - default -> fail("Invalid heading type - {} cannot be mapped", propertyType); - } + @Test + void testExtractAuthoritySftHeadingsWithRelationships() { + final AuthorityDto authorityDto = getAuthorityDtoWithSftTerms(); + final List expectedHeadingRefs = getHeadingRefs(); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + AuthorityUtilityMapper.extractAuthoritySftHeadings(authorityDto, target); - List additionalHeadings = target.getAdditionalHeadings(); - String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); - assertThat(additionalHeadings).hasSize(propertyValues.size()); - additionalHeadings.forEach(a -> assertEquals(propertyType, a.getHeadingType())); - assertArrayEquals(propertyValues.toArray(), targetHeadingValues); + List sftHeadings = target.getSftHeadings(); + assertThat(sftHeadings).hasSize(11); + assertArrayEquals(expectedHeadingRefs.toArray(), sftHeadings.toArray()); } - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityAdditionalHeadingsWithNullValues(String propertyType, List propertyValues) { - switch (propertyType) { - case BROADER_TERM -> source.setBroaderTerm(null); - case NARROWER_TERM -> source.setNarrowerTerm(null); - case EARLIER_HEADING -> source.setEarlierHeading(null); - case LATER_HEADING -> source.setLaterHeading(null); - case SAFT_TERM -> source.setSaftTerm(null); - case SFT_TERM -> source.setSftTerm(null); - default -> fail("Invalid heading type - {} cannot be mapped", propertyType); - } + @Test + void testExtractAuthorityDtoSftHeadingsWithRelationships() { + final List sftHeadings = getHeadingRefs(); + target.setSftHeadings(sftHeadings); + final AuthorityDto expectedAuthorityDto = getAuthorityDtoWithSftTerms(); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(target, source); - assertThat(target.getAdditionalHeadings()).isEmpty(); + assertEquals(expectedAuthorityDto, source); } @Test - void testExtractAuthorityAdditionalHeadingsWithMixedHeadingTypes() { - source.setBroaderTerm(List.of("boarderTerm1", "boarderTerm2")); - source.setNarrowerTerm(List.of("narrowerTerm")); - source.setEarlierHeading(List.of("earlierHeading")); - source.setLaterHeading(List.of("laterHeading")); - source.setSftTerm(List.of("sftTerm1", "sftTerm2", "sftTerm3")); - source.setSaftTerm(List.of("saftTerm1", "saftTerm2")); - - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); - - List additionalHeadings = target.getAdditionalHeadings(); - String[] targetHeadingTypes = additionalHeadings.stream().map(HeadingRef::getHeadingType).toArray(String[]::new); - String[] targetHeadingValues = additionalHeadings.stream().map(HeadingRef::getHeading).toArray(String[]::new); - assertThat(additionalHeadings).hasSize(10); - assertArrayEquals(new String[]{BROADER_TERM, BROADER_TERM, NARROWER_TERM, EARLIER_HEADING, LATER_HEADING, SFT_TERM, - SFT_TERM, SFT_TERM, SAFT_TERM, SAFT_TERM}, targetHeadingTypes); - assertArrayEquals(new String[]{"boarderTerm1", "boarderTerm2", "narrowerTerm", "earlierHeading", "laterHeading", - "sftTerm1", "sftTerm2", "sftTerm3", "saftTerm1", "saftTerm2"}, targetHeadingValues); - } - - @ParameterizedTest - @MethodSource("additionalHeadingTypeAndValuesProvider") - void testExtractAuthorityDtoAdditionalHeadingsWithNonNullValues(String headingType, List headingValues) { - List additionalHeadings = headingValues.stream().map(hv -> new HeadingRef(headingType, hv)).toList(); - target.setAdditionalHeadings(additionalHeadings); + void testExtractAuthoritySaftHeadingsWithRelationships() { + final AuthorityDto authorityDto = getAuthorityDtoWithSaftTerms(); + final List expectedHeadingRefs = getHeadingRefs(); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + AuthorityUtilityMapper.extractAuthoritySaftHeadings(authorityDto, target); - switch (headingType) { - case BROADER_TERM -> assertArrayEquals(source.getBroaderTerm().toArray(), headingValues.toArray()); - case NARROWER_TERM -> assertArrayEquals(source.getNarrowerTerm().toArray(), headingValues.toArray()); - case EARLIER_HEADING -> assertArrayEquals(source.getEarlierHeading().toArray(), headingValues.toArray()); - case LATER_HEADING -> assertArrayEquals(source.getLaterHeading().toArray(), headingValues.toArray()); - case SFT_TERM -> assertArrayEquals(source.getSftTerm().toArray(), headingValues.toArray()); - case SAFT_TERM -> assertArrayEquals(source.getSaftTerm().toArray(), headingValues.toArray()); - default -> fail("Invalid saft heading type - {} cannot be mapped", headingType); - } + List sftHeadings = target.getSaftHeadings(); + assertThat(sftHeadings).hasSize(11); + assertArrayEquals(expectedHeadingRefs.toArray(), sftHeadings.toArray()); } @Test - void testExtractAuthorityDtoAdditionalHeadingsWithNullValues() { + void testExtractAuthorityDtoSaftHeadingsWithRelationships() { + final List saftHeadings = getHeadingRefs(); + target.setSaftHeadings(saftHeadings); + final AuthorityDto expectedAuthorityDto = getAuthorityDtoWithSaftTerms(); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); + AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(target, source); - assertTrue(source.getBroaderTerm().isEmpty()); - assertTrue(source.getNarrowerTerm().isEmpty()); - assertTrue(source.getEarlierHeading().isEmpty()); - assertTrue(source.getLaterHeading().isEmpty()); - assertTrue(source.getSftTerm().isEmpty()); - assertTrue(source.getSaftTerm().isEmpty()); - } - - @Test - void testExtractAuthorityDtoAdditionalHeadingsWithMixedHeadingTypes() { - List additionalHeadings = new ArrayList<>(); - additionalHeadings.add(new HeadingRef(BROADER_TERM, "broaderTerm")); - additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm1")); - additionalHeadings.add(new HeadingRef(NARROWER_TERM, "narrowerTerm2")); - additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading1")); - additionalHeadings.add(new HeadingRef(EARLIER_HEADING, "earlierHeading2")); - additionalHeadings.add(new HeadingRef(LATER_HEADING, "laterHeading")); - additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm1")); - additionalHeadings.add(new HeadingRef(SFT_TERM, "sftTerm2")); - additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm1")); - additionalHeadings.add(new HeadingRef(SAFT_TERM, "saftTerm2")); - target.setAdditionalHeadings(additionalHeadings); - - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(target, source); - - assertArrayEquals(new String[] {"broaderTerm"}, source.getBroaderTerm().toArray()); - assertArrayEquals(new String[] {"narrowerTerm1", "narrowerTerm2"}, source.getNarrowerTerm().toArray()); - assertArrayEquals(new String[] {"earlierHeading1", "earlierHeading2"}, source.getEarlierHeading().toArray()); - assertArrayEquals(new String[] {"laterHeading"}, source.getLaterHeading().toArray()); - assertArrayEquals(new String[] {"sftTerm1", "sftTerm2"}, source.getSftTerm().toArray()); - assertArrayEquals(new String[] {"saftTerm1", "saftTerm2"}, source.getSaftTerm().toArray()); + assertEquals(expectedAuthorityDto, source); } private static Stream headingTypeAndValueProvider() { @@ -323,15 +247,45 @@ private static Stream headingTypeAndValueProvider() { ); } - private static Stream additionalHeadingTypeAndValuesProvider() { - return Stream.of( - arguments(BROADER_TERM, List.of(TEST_STRING)), - arguments(NARROWER_TERM, List.of(TEST_STRING, TEST_STRING)), - arguments(EARLIER_HEADING, List.of(TEST_STRING)), - arguments(LATER_HEADING, List.of(TEST_STRING, TEST_STRING)), - arguments(SFT_TERM, List.of(TEST_STRING, TEST_STRING)), - arguments(SAFT_TERM, List.of(TEST_STRING, TEST_STRING, TEST_STRING)), - arguments(SAFT_TERM, List.of()) - ); + private static List getHeadingRefs() { + return List.of( + new HeadingRef(PERSONAL_NAME_HEADING, PERSONAL_NAME_HEADING), + new HeadingRef(PERSONAL_NAME_HEADING, "broaderTerm1", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(CORPORATE_NAME_HEADING, CORPORATE_NAME_HEADING), + new HeadingRef(CORPORATE_NAME_HEADING, "broaderTerm2", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(CORPORATE_NAME_HEADING, "laterHeading", Set.of(RelationshipType.LATER_HEADING)), + new HeadingRef(MEETING_NAME_HEADING, MEETING_NAME_HEADING), + new HeadingRef(MEETING_NAME_HEADING, "narrowerTerm", Set.of(RelationshipType.NARROWER_TERM)), + new HeadingRef(MEETING_NAME_HEADING, "narrower-later", Set.of(RelationshipType.NARROWER_TERM, + RelationshipType.LATER_HEADING)), + new HeadingRef(TOPICAL_TERM_HEADING, TOPICAL_TERM_HEADING), + new HeadingRef(TOPICAL_TERM_HEADING, "broaderTerm1", Set.of(RelationshipType.BROADER_TERM)), + new HeadingRef(TOPICAL_TERM_HEADING, "earlierHeading", Set.of(RelationshipType.EARLIER_HEADING))); + } + + private static AuthorityDto getAuthorityDtoWithSftTerms() { + AuthorityDto authorityDto = new AuthorityDto(); + authorityDto.setSftBroaderTerm(List.of("broaderTerm1", "broaderTerm2")); + authorityDto.setSftNarrowerTerm(List.of("narrowerTerm", "narrower-later")); + authorityDto.setSftEarlierHeading(List.of("earlierHeading")); + authorityDto.setSftLaterHeading(List.of("laterHeading", "narrower-later")); + authorityDto.setSftPersonalName(List.of(PERSONAL_NAME_HEADING, "broaderTerm1")); + authorityDto.setSftCorporateName(List.of(CORPORATE_NAME_HEADING, "broaderTerm2", "laterHeading")); + authorityDto.setSftMeetingName(List.of(MEETING_NAME_HEADING, "narrowerTerm", "narrower-later")); + authorityDto.setSftTopicalTerm(List.of(TOPICAL_TERM_HEADING, "broaderTerm1", "earlierHeading")); + return authorityDto; + } + + private static AuthorityDto getAuthorityDtoWithSaftTerms() { + AuthorityDto authorityDto = new AuthorityDto(); + authorityDto.setSaftBroaderTerm(List.of("broaderTerm1", "broaderTerm2")); + authorityDto.setSaftNarrowerTerm(List.of("narrowerTerm", "narrower-later")); + authorityDto.setSaftEarlierHeading(List.of("earlierHeading")); + authorityDto.setSaftLaterHeading(List.of("laterHeading", "narrower-later")); + authorityDto.setSaftPersonalName(List.of(PERSONAL_NAME_HEADING, "broaderTerm1")); + authorityDto.setSaftCorporateName(List.of(CORPORATE_NAME_HEADING, "broaderTerm2", "laterHeading")); + authorityDto.setSaftMeetingName(List.of(MEETING_NAME_HEADING, "narrowerTerm", "narrower-later")); + authorityDto.setSaftTopicalTerm(List.of(TOPICAL_TERM_HEADING, "broaderTerm1", "earlierHeading")); + return authorityDto; } } diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 79e12863..412a69c3 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,12 +130,6 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); - existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), - new HeadingRef("narrowerTerm", "narrower"), - new HeadingRef("earlierHeading", "earlier"), - new HeadingRef("laterHeading", "later"), - new HeadingRef("sftTerm", "sft"), - new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -151,12 +145,6 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); - modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), - new HeadingRef("narrowerTerm", "narrowerNew"), - new HeadingRef("earlierHeading", "earlierNew"), - new HeadingRef("laterHeading", "laterNew"), - new HeadingRef("sftTerm", "sftNew"), - new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew); From 738c8054f26d7b8999923a04d0152ae57bf3777a Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 30 Aug 2024 18:04:19 +0300 Subject: [PATCH 09/12] feature/MODELINKS-248 Cover new fields with integration tests --- .../org/folio/entlinks/controller/AuthorityControllerIT.java | 4 ++++ src/test/java/org/folio/support/TestDataUtils.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java b/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java index 9a1ed8f4..dfaeb961 100644 --- a/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java +++ b/src/test/java/org/folio/entlinks/controller/AuthorityControllerIT.java @@ -417,6 +417,8 @@ void createAuthority_positive_entityCreatedWithNewId() throws Exception { assertEquals(dto.getIdentifiers(), created.getIdentifiers()); assertEquals(dto.getSftPersonalName(), created.getSftPersonalName()); assertEquals(dto.getSaftPersonalName(), created.getSaftPersonalName()); + assertEquals(dto.getSftNarrowerTerm(), created.getSftNarrowerTerm()); + assertEquals(dto.getSaftBroaderTerm(), created.getSaftBroaderTerm()); } @Test @@ -521,6 +523,8 @@ void updateAuthority_positive_entityUpdated() throws Exception { assertEquals(expected.getSaftPersonalName(), resultDto.getSaftPersonalName()); assertEquals(expected.getSftCorporateName(), resultDto.getSftCorporateName()); assertEquals(expected.getSaftCorporateName(), resultDto.getSaftCorporateName()); + assertEquals(dto.getSftNarrowerTerm(), resultDto.getSftNarrowerTerm()); + assertEquals(dto.getSaftBroaderTerm(), resultDto.getSaftBroaderTerm()); var event = getConsumedEvent(); awaitUntilAsserted(() -> diff --git a/src/test/java/org/folio/support/TestDataUtils.java b/src/test/java/org/folio/support/TestDataUtils.java index 2c29083e..8bfa5a30 100644 --- a/src/test/java/org/folio/support/TestDataUtils.java +++ b/src/test/java/org/folio/support/TestDataUtils.java @@ -412,6 +412,9 @@ public static AuthorityDto authorityDto(int authorityIdNum, int sourceFileIdNum) dto.addSftMeetingNameItem("sftMeetingNameItem2"); dto.addSaftMeetingNameItem("sftMeetingNameItem1"); dto.addSaftMeetingNameItem("sftMeetingNameItem2"); + dto.addSftNarrowerTermItem("sftPersonalName2"); + dto.addSftNarrowerTermItem("sftMeetingNameItem1"); + dto.addSaftBroaderTermItem("saftPersonalName1"); return dto; } From f9bf68c6429a705672188f837a3c91af58d63331 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 6 Sep 2024 07:36:38 +0300 Subject: [PATCH 10/12] feature/MODELINKS-248 Add description to authorityStatsDto schema --- .../schemas/authority/control/authorityStatsDto.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json b/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json index 9842e9da..bbf0cfc8 100644 --- a/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json +++ b/src/main/resources/swagger.api/schemas/authority/control/authorityStatsDto.json @@ -14,7 +14,8 @@ "description": "Authority ID" }, "action": { - "$ref": "linkAction.json" + "$ref": "linkAction.json", + "description": "Type of change" }, "naturalIdOld": { "type": "string", @@ -61,7 +62,8 @@ "description": "Amount of linked bib fields that was failed to update during authority update" }, "metadata": { - "$ref": "authorityControlMetadata.json" + "$ref": "authorityControlMetadata.json", + "description": "Meta data for authority control" } } } From 5d3559446ddea98b6391c5c341a88265800840d0 Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Thu, 22 Aug 2024 13:03:48 +0300 Subject: [PATCH 11/12] feature/MODELINKS-248 Extend-authorities --- .../controller/converter/AuthorityMapper.java | 2 ++ .../entlinks/domain/entity/AuthorityBase.java | 8 +++++ .../domain/entity/AuthorityConstants.java | 12 ++++++++ .../db/changelog/changelog-master.xml | 2 ++ ...additional-fields-to-authority-archive.xml | 19 ++++++++++++ .../add-additional-fields-to-authority.xml | 19 ++++++++++++ .../authority-storage/authorityDto.yaml | 30 +++++++++++++++++++ .../authority/AuthorityServiceTest.java | 12 ++++++++ 8 files changed, 104 insertions(+) create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml create mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 5c4c49f5..21aeb963 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,6 +93,7 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -100,6 +101,7 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); + AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index dee2764e..2d7b0a9f 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,6 +42,7 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; + private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -90,6 +91,10 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; + @Column(name = ADDITIONAL_HEADINGS_COLUMN) + @JdbcTypeCode(SqlTypes.JSON) + private List additionalHeadings; + public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -116,6 +121,9 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; + this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() + .map(HeadingRef::new) + .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index 337529a3..feb80395 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,4 +24,16 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; + + public static final String BROADER_TERM = "broaderTerm"; + + public static final String NARROWER_TERM = "narrowerTerm"; + + public static final String EARLIER_HEADING = "earlierHeading"; + + public static final String LATER_HEADING = "laterHeading"; + + public static final String SFT_TERM = "sftTerm"; + + public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 906c7bf5..a4417124 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,4 +23,6 @@ + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml new file mode 100644 index 00000000..404ed022 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority_archive table + + + + + + + diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml new file mode 100644 index 00000000..80db8253 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml @@ -0,0 +1,19 @@ + + + + + + + + + Add new jsonb fields to authority table + + + + + + + diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index 9e67295e..1baa13ff 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -203,6 +203,36 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' + broaderTerm: + type: array + description: Terms that represents broader, more general concepts related to the authority record + items: + type: string + narrowerTerm: + type: array + description: Terms that represents narrower, more specific concepts derived from the authority record + items: + type: string + earlierHeading: + type: array + description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. + items: + type: string + laterHeading: + type: array + description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. + items: + type: string + sftTerm: + type: array + description: See from tracing term + items: + type: string + saftTerm: + type: array + description: See also from tracing term + items: + type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 412a69c3..79e12863 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,6 +130,12 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); + existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), + new HeadingRef("narrowerTerm", "narrower"), + new HeadingRef("earlierHeading", "earlier"), + new HeadingRef("laterHeading", "later"), + new HeadingRef("sftTerm", "sft"), + new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -145,6 +151,12 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); + modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), + new HeadingRef("narrowerTerm", "narrowerNew"), + new HeadingRef("earlierHeading", "earlierNew"), + new HeadingRef("laterHeading", "laterNew"), + new HeadingRef("sftTerm", "sftNew"), + new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew); From bb6ae5cf4139ef7d1871d5269ecf0ee49c533c9b Mon Sep 17 00:00:00 2001 From: ElenaShm Date: Fri, 30 Aug 2024 16:05:47 +0300 Subject: [PATCH 12/12] feature/MODELINKS-248 split relationship fields to sft and saft --- .../controller/converter/AuthorityMapper.java | 2 -- .../entlinks/domain/entity/AuthorityBase.java | 8 ----- .../domain/entity/AuthorityConstants.java | 12 -------- .../db/changelog/changelog-master.xml | 2 -- ...additional-fields-to-authority-archive.xml | 19 ------------ .../add-additional-fields-to-authority.xml | 19 ------------ .../authority-storage/authorityDto.yaml | 30 ------------------- .../authority/AuthorityServiceTest.java | 12 -------- 8 files changed, 104 deletions(-) delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml delete mode 100644 src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml diff --git a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java index 21aeb963..5c4c49f5 100644 --- a/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java +++ b/src/main/java/org/folio/entlinks/controller/converter/AuthorityMapper.java @@ -93,7 +93,6 @@ default void authorityPostProcess(AuthorityDto source, @MappingTarget AuthorityB AuthorityUtilityMapper.extractAuthorityHeading(source, target); AuthorityUtilityMapper.extractAuthoritySftHeadings(source, target); AuthorityUtilityMapper.extractAuthoritySaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityAdditionalHeadings(source, target); } @AfterMapping @@ -101,7 +100,6 @@ default void authorityDtoPostProcessing(AuthorityBase source, @MappingTarget Aut AuthorityUtilityMapper.extractAuthorityDtoHeadingValue(source, target); AuthorityUtilityMapper.extractAuthorityDtoSftHeadings(source, target); AuthorityUtilityMapper.extractAuthorityDtoSaftHeadings(source, target); - AuthorityUtilityMapper.extractAuthorityDtoAdditionalHeadings(source, target); } default OffsetDateTime map(Timestamp timestamp) { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java index 2d7b0a9f..dee2764e 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityBase.java @@ -42,7 +42,6 @@ public class AuthorityBase extends MetadataEntity { public static final String DELETED_COLUMN = "deleted"; private static final String CONSORTIUM_SOURCE_PREFIX = "CONSORTIUM-"; - private static final String ADDITIONAL_HEADINGS_COLUMN = "additional_headings"; @Id @Column(name = ID_COLUMN, nullable = false) @@ -91,10 +90,6 @@ public class AuthorityBase extends MetadataEntity { @Column(name = DELETED_COLUMN) private boolean deleted = false; - @Column(name = ADDITIONAL_HEADINGS_COLUMN) - @JdbcTypeCode(SqlTypes.JSON) - private List additionalHeadings; - public AuthorityBase(AuthorityBase other) { super(other); this.id = other.id; @@ -121,9 +116,6 @@ public AuthorityBase(AuthorityBase other) { .map(AuthorityNote::new) .toList(); this.deleted = other.deleted; - this.additionalHeadings = Optional.ofNullable(other.getSaftHeadings()).orElse(List.of()).stream() - .map(HeadingRef::new) - .toList(); } public void makeAsConsortiumShadowCopy() { diff --git a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java index feb80395..337529a3 100644 --- a/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java +++ b/src/main/java/org/folio/entlinks/domain/entity/AuthorityConstants.java @@ -24,16 +24,4 @@ public class AuthorityConstants { public static final String GEOGRAPHIC_NAME_HEADING = "geographicName"; public static final String GENRE_TERM_HEADING = "genreTerm"; - - public static final String BROADER_TERM = "broaderTerm"; - - public static final String NARROWER_TERM = "narrowerTerm"; - - public static final String EARLIER_HEADING = "earlierHeading"; - - public static final String LATER_HEADING = "laterHeading"; - - public static final String SFT_TERM = "sftTerm"; - - public static final String SAFT_TERM = "saftTerm"; } diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index a4417124..906c7bf5 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -23,6 +23,4 @@ - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml deleted file mode 100644 index 404ed022..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority-archive.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority_archive table - - - - - - - diff --git a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml b/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml deleted file mode 100644 index 80db8253..00000000 --- a/src/main/resources/db/changelog/changes/v3.1/add-additional-fields-to-authority.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - Add new jsonb fields to authority table - - - - - - - diff --git a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml index 1baa13ff..9e67295e 100644 --- a/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml +++ b/src/main/resources/swagger.api/schemas/authority-storage/authorityDto.yaml @@ -203,36 +203,6 @@ properties: description: Authority Natural ID metadata: $ref: '../common/metadata.yaml' - broaderTerm: - type: array - description: Terms that represents broader, more general concepts related to the authority record - items: - type: string - narrowerTerm: - type: array - description: Terms that represents narrower, more specific concepts derived from the authority record - items: - type: string - earlierHeading: - type: array - description: Heading that was previously used to represent the concept or entity described by the authority record. This field is used to track the evolution of terms or headings over time, facilitating the linking of historical and current data. - items: - type: string - laterHeading: - type: array - description: Heading that replaced the current heading used in the authority record. This field helps in maintaining the continuity of catalog records by linking past headings to their more current versions. - items: - type: string - sftTerm: - type: array - description: See from tracing term - items: - type: string - saftTerm: - type: array - description: See also from tracing term - items: - type: string required: - source - naturalId diff --git a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java index 79e12863..412a69c3 100644 --- a/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java +++ b/src/test/java/org/folio/entlinks/service/authority/AuthorityServiceTest.java @@ -130,12 +130,6 @@ void shouldUpdateAuthority() { existed.setSftHeadings(List.of(new HeadingRef("personalName", "sft"))); existed.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "note", true))); existed.setIdentifiers(List.of(new AuthorityIdentifier("identifier", UUID.randomUUID()))); - existed.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broader"), - new HeadingRef("narrowerTerm", "narrower"), - new HeadingRef("earlierHeading", "earlier"), - new HeadingRef("laterHeading", "later"), - new HeadingRef("sftTerm", "sft"), - new HeadingRef("saftTerm", "saft"))); var sourceFileOld = new AuthoritySourceFile(); sourceFileOld.setId(UUID.randomUUID()); existed.setAuthoritySourceFile(sourceFileOld); @@ -151,12 +145,6 @@ void shouldUpdateAuthority() { modified.setSftHeadings(List.of(new HeadingRef("personalNameNew", "sftNew"))); modified.setNotes(List.of(new AuthorityNote(UUID.randomUUID(), "noteNew", true))); modified.setIdentifiers(List.of(new AuthorityIdentifier("identifierNew", UUID.randomUUID()))); - modified.setAdditionalHeadings(List.of(new HeadingRef("broaderTerm", "broaderNew"), - new HeadingRef("narrowerTerm", "narrowerNew"), - new HeadingRef("earlierHeading", "earlierNew"), - new HeadingRef("laterHeading", "laterNew"), - new HeadingRef("sftTerm", "sftNew"), - new HeadingRef("saftTerm", "saftNew"))); var sourceFileNew = new AuthoritySourceFile(); sourceFileNew.setId(UUID.randomUUID()); modified.setAuthoritySourceFile(sourceFileNew);