From 11df62ded7dd87cb0d217e1cf212710cd33b5b1c Mon Sep 17 00:00:00 2001 From: AndreiBordak Date: Mon, 6 Jan 2025 18:14:12 +0400 Subject: [PATCH] MODLD-516: Update API to support IllustrativeContent of the Work (#85) * MODLD-516: Update API to support IllustrativeContent of the Work * MODLD-516: Review fixes * MODLD-516: Change mapper package --- .../dto/monograph/work/WorkMapperUnit.java | 3 ++ .../work/sub/IllustrationsMapperUnit.java | 48 +++++++++++++++++ .../schema/resource/request/WorkRequest.json | 8 +++ .../resource/response/WorkResponse.json | 8 +++ .../resource/ResourceControllerITBase.java | 52 ++++++++++++++++--- .../linked/data/test/MonographTestUtil.java | 12 +++++ .../data/test/resource/ResourceJsonPath.java | 13 +++++ .../samples/work_and_instance_ref.json | 13 +++++ 8 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/IllustrationsMapperUnit.java diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/WorkMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/WorkMapperUnit.java index e3850dc4..10b21ea8 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/WorkMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/WorkMapperUnit.java @@ -9,6 +9,7 @@ import static org.folio.ld.dictionary.PredicateDictionary.GENRE; import static org.folio.ld.dictionary.PredicateDictionary.GEOGRAPHIC_COVERAGE; import static org.folio.ld.dictionary.PredicateDictionary.GOVERNMENT_PUBLICATION; +import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS; import static org.folio.ld.dictionary.PredicateDictionary.INSTANTIATES; import static org.folio.ld.dictionary.PredicateDictionary.LANGUAGE; import static org.folio.ld.dictionary.PredicateDictionary.ORIGIN_PLACE; @@ -88,6 +89,8 @@ public Resource toEntity(Object dto, Resource parentEntity) { coreMapper.addOutgoingEdges(work, WorkRequest.class, workDto.getTargetAudience(), TARGET_AUDIENCE); coreMapper.addOutgoingEdges(work, WorkRequest.class, workDto.getLanguage(), LANGUAGE); coreMapper.addIncomingEdges(work, WorkRequest.class, workDto.getInstanceReference(), INSTANTIATES); + coreMapper.addOutgoingEdges(work, WorkRequest.class, workDto.getIllustrations(), ILLUSTRATIONS); + work.setId(hashService.hash(work)); return work; } diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/IllustrationsMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/IllustrationsMapperUnit.java new file mode 100644 index 00000000..d71ba039 --- /dev/null +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/IllustrationsMapperUnit.java @@ -0,0 +1,48 @@ +package org.folio.linked.data.mapper.dto.monograph.work.sub; + +import static org.folio.ld.dictionary.ResourceTypeDictionary.CATEGORY; + +import org.folio.ld.dictionary.PredicateDictionary; +import org.folio.linked.data.domain.dto.Category; +import org.folio.linked.data.domain.dto.CategoryResponse; +import org.folio.linked.data.domain.dto.WorkResponse; +import org.folio.linked.data.mapper.dto.common.CoreMapper; +import org.folio.linked.data.mapper.dto.common.MapperUnit; +import org.folio.linked.data.mapper.dto.monograph.common.CategoryMapperUnit; +import org.folio.linked.data.service.resource.hash.HashService; +import org.springframework.stereotype.Component; + +@Component +@MapperUnit(type = CATEGORY, predicate = PredicateDictionary.ILLUSTRATIONS, requestDto = Category.class) +public class IllustrationsMapperUnit extends CategoryMapperUnit { + + private static final String LABEL = "Illustrative Content"; + private static final String LINK = "http://id.loc.gov/vocabulary/millus"; + private static final String LINK_PREFIX = "http://id.loc.gov/vocabulary/millus/"; + + public IllustrationsMapperUnit(HashService hashService, CoreMapper coreMapper) { + super(hashService, coreMapper); + } + + @Override + protected void addToParent(CategoryResponse category, Object parentDto) { + if (parentDto instanceof WorkResponse work) { + work.addIllustrationsItem(category); + } + } + + @Override + protected String getCategorySetLabel() { + return LABEL; + } + + @Override + protected String getCategorySetLink() { + return LINK; + } + + @Override + public String getLinkPrefix() { + return LINK_PREFIX; + } +} diff --git a/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json b/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json index 09abfb15..a130537d 100644 --- a/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json +++ b/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json @@ -142,6 +142,14 @@ "$ref": "../common/Dissertation.json" }, "x-json-property": "http://bibfra.me/vocab/scholar/dissertation" + }, + "illustrations": { + "type": "array", + "items": { + "type": "object", + "$ref": "../common/Category.json" + }, + "x-json-property": "http://bibfra.me/vocab/marc/illustrations" } } } diff --git a/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json b/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json index dcb71034..77d7d308 100644 --- a/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json +++ b/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json @@ -145,6 +145,14 @@ "$ref": "DissertationResponse.json" }, "x-json-property": "http://bibfra.me/vocab/scholar/dissertation" + }, + "illustrations": { + "type": "array", + "items": { + "type": "object", + "$ref": "CategoryResponse.json" + }, + "x-json-property": "http://bibfra.me/vocab/marc/illustrations" } } } diff --git a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java index e059d72c..b513b927 100644 --- a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java +++ b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java @@ -20,6 +20,7 @@ import static org.folio.ld.dictionary.PredicateDictionary.GENRE; import static org.folio.ld.dictionary.PredicateDictionary.GEOGRAPHIC_COVERAGE; import static org.folio.ld.dictionary.PredicateDictionary.GOVERNMENT_PUBLICATION; +import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS; import static org.folio.ld.dictionary.PredicateDictionary.INSTANTIATES; import static org.folio.ld.dictionary.PredicateDictionary.IS_DEFINED_BY; import static org.folio.ld.dictionary.PredicateDictionary.LANGUAGE; @@ -152,6 +153,9 @@ import static org.folio.linked.data.test.resource.ResourceJsonPath.toEditionStatement; import static org.folio.linked.data.test.resource.ResourceJsonPath.toExtent; import static org.folio.linked.data.test.resource.ResourceJsonPath.toId; +import static org.folio.linked.data.test.resource.ResourceJsonPath.toIllustrationsCode; +import static org.folio.linked.data.test.resource.ResourceJsonPath.toIllustrationsLink; +import static org.folio.linked.data.test.resource.ResourceJsonPath.toIllustrationsTerm; import static org.folio.linked.data.test.resource.ResourceJsonPath.toInstance; import static org.folio.linked.data.test.resource.ResourceJsonPath.toInstanceNotesTypes; import static org.folio.linked.data.test.resource.ResourceJsonPath.toInstanceNotesValues; @@ -614,8 +618,8 @@ void deleteResourceById_shouldDeleteRootInstanceAndRootEdges_reindexWork() throw var work = getSampleWork(null); var instance = resourceTestService.saveGraph(getSampleInstanceResource(null, work)); assertThat(resourceTestService.findById(instance.getId())).isPresent(); - assertThat(resourceTestService.countResources()).isEqualTo(57); - assertThat(resourceTestService.countEdges()).isEqualTo(59); + assertThat(resourceTestService.countResources()).isEqualTo(58); + assertThat(resourceTestService.countEdges()).isEqualTo(60); var requestBuilder = delete(RESOURCE_URL + "/" + instance.getId()) .contentType(APPLICATION_JSON) .headers(defaultHeaders(env)); @@ -626,9 +630,9 @@ void deleteResourceById_shouldDeleteRootInstanceAndRootEdges_reindexWork() throw // then resultActions.andExpect(status().isNoContent()); assertThat(resourceTestService.existsById(instance.getId())).isFalse(); - assertThat(resourceTestService.countResources()).isEqualTo(56); + assertThat(resourceTestService.countResources()).isEqualTo(57); assertThat(resourceTestService.findEdgeById(instance.getOutgoingEdges().iterator().next().getId())).isNotPresent(); - assertThat(resourceTestService.countEdges()).isEqualTo(41); + assertThat(resourceTestService.countEdges()).isEqualTo(42); checkSearchIndexMessage(work.getId(), UPDATE); checkIndexDate(work.getId().toString()); } @@ -638,8 +642,8 @@ void deleteResourceById_shouldDeleteRootWorkAndRootEdges() throws Exception { // given var existed = resourceTestService.saveGraph(getSampleWork(getSampleInstanceResource(null, null))); assertThat(resourceTestService.findById(existed.getId())).isPresent(); - assertThat(resourceTestService.countResources()).isEqualTo(57); - assertThat(resourceTestService.countEdges()).isEqualTo(59); + assertThat(resourceTestService.countResources()).isEqualTo(58); + assertThat(resourceTestService.countEdges()).isEqualTo(60); var requestBuilder = delete(RESOURCE_URL + "/" + existed.getId()) .contentType(APPLICATION_JSON) .headers(defaultHeaders(env)); @@ -650,7 +654,7 @@ void deleteResourceById_shouldDeleteRootWorkAndRootEdges() throws Exception { // then resultActions.andExpect(status().isNoContent()); assertThat(resourceTestService.existsById(existed.getId())).isFalse(); - assertThat(resourceTestService.countResources()).isEqualTo(56); + assertThat(resourceTestService.countResources()).isEqualTo(57); assertThat(resourceTestService.findEdgeById(existed.getOutgoingEdges().iterator().next().getId())).isNotPresent(); assertThat(resourceTestService.countEdges()).isEqualTo(30); checkSearchIndexMessage(existed.getId(), DELETE); @@ -863,7 +867,10 @@ private void validateWorkResponse(ResultActions resultActions, String workBase) .andExpect(jsonPath(toWorkGovPublicationLink(workBase), equalTo("http://id.loc.gov/vocabulary/mgovtpubtype/a"))) .andExpect(jsonPath(toWorkTargetAudienceCode(workBase), equalTo("b"))) .andExpect(jsonPath(toWorkTargetAudienceTerm(workBase), equalTo("Primary"))) - .andExpect(jsonPath(toWorkTargetAudienceLink(workBase), equalTo("http://id.loc.gov/vocabulary/maudience/pri"))); + .andExpect(jsonPath(toWorkTargetAudienceLink(workBase), equalTo("http://id.loc.gov/vocabulary/maudience/pri"))) + .andExpect(jsonPath(toIllustrationsCode(workBase), equalTo("code"))) + .andExpect(jsonPath(toIllustrationsLink(workBase), equalTo("http://id.loc.gov/vocabulary/millus/code"))) + .andExpect(jsonPath(toIllustrationsTerm(workBase), equalTo("illustrations term"))); if (workBase.equals(toWork())) { resultActions.andExpect(jsonPath(toInstanceReference(workBase), notNullValue())); validateInstanceResponse(resultActions, toInstanceReference(workBase)); @@ -1188,6 +1195,27 @@ private void validateAccessLocation(ResourceEdge edge, Resource source) { assertThat(locator.getOutgoingEdges()).isEmpty(); } + private void validateCategory(ResourceEdge edge, + Resource source, + PredicateDictionary pred, + String label, + Map doc, + String categorySetLabel) { + assertThat(edge.getId()).isNotNull(); + assertThat(edge.getSource()).isEqualTo(source); + assertThat(edge.getPredicate().getUri()).isEqualTo(pred.getUri()); + var category = edge.getTarget(); + assertThat(category.getLabel()).isEqualTo(label); + assertThat(category.getTypes().iterator().next().getUri()).isEqualTo(CATEGORY.getUri()); + assertThat(category.getId()).isEqualTo(hashService.hash(category)); + doc.forEach((key, value) -> validateLiteral(category, key, value)); + if (category.getOutgoingEdges().isEmpty()) { + return; + } + assertCategorySetIsDefinedBy(category); + assertEquals(category.getOutgoingEdges().iterator().next().getTarget().getLabel(), categorySetLabel); + } + private void validateCategory(ResourceEdge edge, Resource source, PredicateDictionary pred, String expectedLink, String expectedCode) { var prefix = pred.getUri().substring(pred.getUri().lastIndexOf("/") + 1); @@ -1206,6 +1234,10 @@ private void validateCategory(ResourceEdge edge, Resource source, PredicateDicti if (category.getOutgoingEdges().isEmpty()) { return; } + assertCategorySetIsDefinedBy(category); + } + + private void assertCategorySetIsDefinedBy(Resource category) { assertThat(category.getOutgoingEdges()) .extracting(ResourceEdge::getPredicate) .extracting(PredicateEntity::getUri) @@ -1233,6 +1265,10 @@ private void validateWork(Resource work, boolean validateFullInstance) { validateParallelTitle(outgoingEdgeIterator.next(), work); validateWorkContentType(outgoingEdgeIterator.next(), work); validateWorkTargetAudience(outgoingEdgeIterator.next(), work); + validateCategory(outgoingEdgeIterator.next(), work, ILLUSTRATIONS, "illustrations term", + Map.of(LINK.getValue(), "http://id.loc.gov/vocabulary/millus/code", CODE.getValue(), "code"), + "Illustrative Content" + ); validateWorkGovernmentPublication(outgoingEdgeIterator.next(), work); validateLanguage(outgoingEdgeIterator.next(), work); validateDissertation(outgoingEdgeIterator.next(), work); diff --git a/src/test/java/org/folio/linked/data/test/MonographTestUtil.java b/src/test/java/org/folio/linked/data/test/MonographTestUtil.java index 80180a76..7fd54623 100644 --- a/src/test/java/org/folio/linked/data/test/MonographTestUtil.java +++ b/src/test/java/org/folio/linked/data/test/MonographTestUtil.java @@ -18,6 +18,7 @@ import static org.folio.ld.dictionary.PredicateDictionary.GENRE; import static org.folio.ld.dictionary.PredicateDictionary.GOVERNMENT_PUBLICATION; import static org.folio.ld.dictionary.PredicateDictionary.GRANTING_INSTITUTION; +import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS; import static org.folio.ld.dictionary.PredicateDictionary.INSTANTIATES; import static org.folio.ld.dictionary.PredicateDictionary.IS_DEFINED_BY; import static org.folio.ld.dictionary.PredicateDictionary.LANGUAGE; @@ -517,6 +518,16 @@ public static Resource getSampleWork(Resource linkedInstance) { emptyMap() ).setLabel("eng"); + var illustrations = createResource( + Map.of( + CODE, List.of("code"), + TERM, List.of("illustrations term"), + LINK, List.of("http://id.loc.gov/vocabulary/millus/code") + ), + Set.of(CATEGORY), + emptyMap() + ).setLabel("illustrations term"); + var pred2OutgoingResources = new LinkedHashMap>(); pred2OutgoingResources.put(TITLE, List.of(primaryTitle, createParallelTitle(), createVariantTitle())); pred2OutgoingResources.put(CLASSIFICATION, List.of(createLcClassification(), createDeweyClassification())); @@ -535,6 +546,7 @@ public static Resource getSampleWork(Resource linkedInstance) { pred2OutgoingResources.put(DISSERTATION, List.of(createDissertation())); pred2OutgoingResources.put(TARGET_AUDIENCE, List.of(createTargetAudience())); pred2OutgoingResources.put(LANGUAGE, List.of(language)); + pred2OutgoingResources.put(ILLUSTRATIONS, List.of(illustrations)); var work = createResource( Map.ofEntries( diff --git a/src/test/java/org/folio/linked/data/test/resource/ResourceJsonPath.java b/src/test/java/org/folio/linked/data/test/resource/ResourceJsonPath.java index 29935e57..ca33c743 100644 --- a/src/test/java/org/folio/linked/data/test/resource/ResourceJsonPath.java +++ b/src/test/java/org/folio/linked/data/test/resource/ResourceJsonPath.java @@ -9,6 +9,7 @@ import static org.folio.ld.dictionary.PredicateDictionary.COPYRIGHT; import static org.folio.ld.dictionary.PredicateDictionary.DISSERTATION; import static org.folio.ld.dictionary.PredicateDictionary.GOVERNMENT_PUBLICATION; +import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS; import static org.folio.ld.dictionary.PredicateDictionary.LANGUAGE; import static org.folio.ld.dictionary.PredicateDictionary.MAP; import static org.folio.ld.dictionary.PredicateDictionary.MEDIA; @@ -351,6 +352,18 @@ public static String toCarrierTerm(String instanceBase) { return join(".", instanceBase, arrayPath(CARRIER.getUri()), arrayPath(TERM.getValue())); } + public static String toIllustrationsCode(String workBase) { + return join(".", workBase, arrayPath(ILLUSTRATIONS.getUri()), arrayPath(CODE.getValue())); + } + + public static String toIllustrationsLink(String workBase) { + return join(".", workBase, arrayPath(ILLUSTRATIONS.getUri()), arrayPath(LINK.getValue())); + } + + public static String toIllustrationsTerm(String workBase) { + return join(".", workBase, arrayPath(ILLUSTRATIONS.getUri()), arrayPath(TERM.getValue())); + } + public static String toCopyrightDate() { return join(".", toInstance(), arrayPath(COPYRIGHT.getUri()), arrayPath(DATE.getValue())); } diff --git a/src/test/resources/samples/work_and_instance_ref.json b/src/test/resources/samples/work_and_instance_ref.json index 457bf3ff..22f7c302 100644 --- a/src/test/resources/samples/work_and_instance_ref.json +++ b/src/test/resources/samples/work_and_instance_ref.json @@ -303,6 +303,19 @@ ] } ], + "http://bibfra.me/vocab/marc/illustrations": [ + { + "http://bibfra.me/vocab/lite/link": [ + "http://id.loc.gov/vocabulary/millus/code" + ], + "http://bibfra.me/vocab/marc/term": [ + "illustrations term" + ], + "http://bibfra.me/vocab/marc/code": [ + "code" + ] + } + ], "http://bibfra.me/vocab/scholar/dissertation": [ { "http://bibfra.me/vocab/lite/label": [