Skip to content

Commit

Permalink
MODLD-308: Enhance GET, PUT and POST APIs to support Dewey Decimal Cl…
Browse files Browse the repository at this point in the history
…… (#261)
  • Loading branch information
askhat-abishev authored May 31, 2024
1 parent 393457f commit 3e9162d
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.folio.linked.data.mapper.dto.monograph.work.sub;

import static org.folio.ld.dictionary.PredicateDictionary.ASSIGNING_SOURCE;
import static org.folio.ld.dictionary.ResourceTypeDictionary.ORGANIZATION;

import java.util.Set;
import org.folio.linked.data.domain.dto.Classification;
import org.folio.linked.data.domain.dto.Reference;
import org.folio.linked.data.mapper.dto.common.MapperUnit;
import org.folio.linked.data.repo.ResourceRepository;
import org.springframework.stereotype.Component;

@Component
@MapperUnit(type = ORGANIZATION, predicate = ASSIGNING_SOURCE, dtoClass = Reference.class)
public class AssigningSourceMapperUnit extends ReferenceMapperUnit {

private static final Set<Class<?>> SUPPORTED_PARENTS = Set.of(Classification.class);

public AssigningSourceMapperUnit(ResourceRepository resourceRepository) {
super((assigningSource, destination) -> {
if (destination instanceof Classification deweyDecimalClassification) {
deweyDecimalClassification.addAssigningSourceReferenceItem(assigningSource);
}
}, resourceRepository);
}

@Override
public Set<Class<?>> supportedParents() {
return SUPPORTED_PARENTS;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package org.folio.linked.data.mapper.dto.monograph.work.sub;

import static org.folio.ld.dictionary.PredicateDictionary.ASSIGNING_SOURCE;
import static org.folio.ld.dictionary.PredicateDictionary.CLASSIFICATION;
import static org.folio.ld.dictionary.PropertyDictionary.CODE;
import static org.folio.ld.dictionary.PropertyDictionary.EDITION;
import static org.folio.ld.dictionary.PropertyDictionary.EDITION_NUMBER;
import static org.folio.ld.dictionary.PropertyDictionary.ITEM_NUMBER;
import static org.folio.ld.dictionary.PropertyDictionary.SOURCE;
import static org.folio.ld.dictionary.ResourceTypeDictionary.CATEGORY;
import static org.folio.linked.data.util.BibframeUtils.putProperty;

import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.folio.linked.data.domain.dto.DeweyDecimalClassification;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.domain.dto.Classification;
import org.folio.linked.data.domain.dto.Work;
import org.folio.linked.data.mapper.dto.common.CoreMapper;
import org.folio.linked.data.mapper.dto.common.MapperUnit;
Expand All @@ -20,15 +24,16 @@

@Component
@RequiredArgsConstructor
@MapperUnit(type = CATEGORY, predicate = CLASSIFICATION, dtoClass = DeweyDecimalClassification.class)
@MapperUnit(type = ResourceTypeDictionary.CLASSIFICATION, predicate = CLASSIFICATION,
dtoClass = Classification.class)
public class DeweyDecimalClassificationMapperUnit implements WorkSubResourceMapperUnit {

private final CoreMapper coreMapper;
private final HashService hashService;

@Override
public <P> P toDto(Resource source, P parentDto, Resource parentResource) {
var deweyDecimalClassification = coreMapper.toDtoWithEdges(source, DeweyDecimalClassification.class, false);
var deweyDecimalClassification = coreMapper.toDtoWithEdges(source, Classification.class, false);
deweyDecimalClassification.setId(String.valueOf(source.getId()));
if (parentDto instanceof Work work) {
work.addClassificationItem(deweyDecimalClassification);
Expand All @@ -38,18 +43,23 @@ public <P> P toDto(Resource source, P parentDto, Resource parentResource) {

@Override
public Resource toEntity(Object dto, Resource parentEntity) {
var deweyDecimalClassification = (DeweyDecimalClassification) dto;
var deweyDecimalClassification = (Classification) dto;
var resource = new Resource();
resource.addTypes(CATEGORY);
resource.addTypes(ResourceTypeDictionary.CLASSIFICATION);
resource.setDoc(getDoc(deweyDecimalClassification));
coreMapper.addOutgoingEdges(resource, Classification.class,
deweyDecimalClassification.getAssigningSourceReference(), ASSIGNING_SOURCE);
resource.setId(hashService.hash(resource));
return resource;
}

private JsonNode getDoc(DeweyDecimalClassification dto) {
private JsonNode getDoc(Classification dto) {
var map = new HashMap<String, List<String>>();
putProperty(map, CODE, dto.getCode());
putProperty(map, SOURCE, dto.getSource());
putProperty(map, ITEM_NUMBER, dto.getItemNumber());
putProperty(map, EDITION_NUMBER, dto.getEditionNumber());
putProperty(map, EDITION, dto.getEdition());
return map.isEmpty() ? null : coreMapper.toJson(map);
}

Expand Down
56 changes: 56 additions & 0 deletions src/main/resources/swagger.api/schema/resource/Classification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Dewey decimal classification object",
"allOf": [
{
"$ref": "Topper.json"
},
{
"type": "object",
"properties": {
"code": {
"type": "array",
"items": {
"type": "string"
},
"x-json-property": "http://bibfra.me/vocab/marc/code"
},
"source": {
"type": "array",
"items": {
"type": "string"
},
"x-json-property": "http://bibfra.me/vocab/marc/source"
},
"itemNumber": {
"type": "array",
"items": {
"type": "string"
},
"x-json-property": "http://bibfra.me/vocab/marc/itemNumber"
},
"editionNumber": {
"type": "array",
"items": {
"type": "string"
},
"x-json-property": "http://bibfra.me/vocab/marc/editionNumber"
},
"edition": {
"type": "array",
"items": {
"type": "string"
},
"x-json-property": "http://bibfra.me/vocab/marc/edition"
},
"_assigningSourceReference": {
"type": "array",
"items": {
"type": "object",
"$ref": "Reference.json"
}
}
}
}
]
}

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/resources/swagger.api/schema/resource/Work.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"type": "array",
"items": {
"type": "object",
"$ref": "DeweyDecimalClassification.json"
"$ref": "Classification.json"
},
"x-json-property": "http://bibfra.me/vocab/lite/classification"
},
Expand Down
73 changes: 57 additions & 16 deletions src/test/java/org/folio/linked/data/e2e/ResourceControllerIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@
import static org.folio.ld.dictionary.PropertyDictionary.DESCRIPTION_SOURCE_NOTE;
import static org.folio.ld.dictionary.PropertyDictionary.DIMENSIONS;
import static org.folio.ld.dictionary.PropertyDictionary.EAN_VALUE;
import static org.folio.ld.dictionary.PropertyDictionary.EDITION;
import static org.folio.ld.dictionary.PropertyDictionary.EDITION_NUMBER;
import static org.folio.ld.dictionary.PropertyDictionary.EDITION_STATEMENT;
import static org.folio.ld.dictionary.PropertyDictionary.EXHIBITIONS_NOTE;
import static org.folio.ld.dictionary.PropertyDictionary.EXTENT;
import static org.folio.ld.dictionary.PropertyDictionary.FUNDING_INFORMATION;
import static org.folio.ld.dictionary.PropertyDictionary.ISSUANCE;
import static org.folio.ld.dictionary.PropertyDictionary.ISSUANCE_NOTE;
import static org.folio.ld.dictionary.PropertyDictionary.ISSUING_BODY;
import static org.folio.ld.dictionary.PropertyDictionary.ITEM_NUMBER;
import static org.folio.ld.dictionary.PropertyDictionary.LABEL;
import static org.folio.ld.dictionary.PropertyDictionary.LANGUAGE;
import static org.folio.ld.dictionary.PropertyDictionary.LANGUAGE_NOTE;
Expand Down Expand Up @@ -185,6 +188,7 @@ class ResourceControllerIT {
private static final String CONTRIBUTOR_REF = "_contributorReference";
private static final String GEOGRAPHIC_COVERAGE_REF = "_geographicCoverageReference";
private static final String GENRE_REF = "_genreReference";
private static final String ASSIGNING_SOURCE_REF = "_assigningSourceReference";
private static final String WORK_ID_PLACEHOLDER = "%WORK_ID%";
private static final String INSTANCE_ID_PLACEHOLDER = "%INSTANCE_ID%";
@Autowired
Expand Down Expand Up @@ -445,8 +449,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(49);
assertThat(resourceTestService.countEdges()).isEqualTo(51);
assertThat(resourceTestService.countResources()).isEqualTo(50);
assertThat(resourceTestService.countEdges()).isEqualTo(52);
var requestBuilder = delete(RESOURCE_URL + "/" + instance.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env));
Expand All @@ -457,9 +461,9 @@ void deleteResourceById_shouldDeleteRootInstanceAndRootEdges_reindexWork() throw
// then
resultActions.andExpect(status().isNoContent());
assertThat(resourceTestService.existsById(instance.getId())).isFalse();
assertThat(resourceTestService.countResources()).isEqualTo(48);
assertThat(resourceTestService.countResources()).isEqualTo(49);
assertThat(resourceTestService.findEdgeById(instance.getOutgoingEdges().iterator().next().getId())).isNotPresent();
assertThat(resourceTestService.countEdges()).isEqualTo(33);
assertThat(resourceTestService.countEdges()).isEqualTo(34);
checkKafkaMessage(work.getId(), UPDATE);
}

Expand All @@ -468,8 +472,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(49);
assertThat(resourceTestService.countEdges()).isEqualTo(51);
assertThat(resourceTestService.countResources()).isEqualTo(50);
assertThat(resourceTestService.countEdges()).isEqualTo(52);
var requestBuilder = delete(RESOURCE_URL + "/" + existed.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env));
Expand All @@ -480,9 +484,9 @@ void deleteResourceById_shouldDeleteRootWorkAndRootEdges() throws Exception {
// then
resultActions.andExpect(status().isNoContent());
assertThat(resourceTestService.existsById(existed.getId())).isFalse();
assertThat(resourceTestService.countResources()).isEqualTo(48);
assertThat(resourceTestService.countResources()).isEqualTo(49);
assertThat(resourceTestService.findEdgeById(existed.getOutgoingEdges().iterator().next().getId())).isNotPresent();
assertThat(resourceTestService.countEdges()).isEqualTo(25);
assertThat(resourceTestService.countEdges()).isEqualTo(26);
checkKafkaMessage(existed.getId(), DELETE);
}

Expand Down Expand Up @@ -647,6 +651,11 @@ private void validateWorkResponse(ResultActions resultActions, String workBase)
.andExpect(jsonPath(toWorkLanguage(workBase), equalTo("eng")))
.andExpect(jsonPath(toWorkDeweyCode(workBase), equalTo("709.83")))
.andExpect(jsonPath(toWorkDeweySource(workBase), equalTo("ddc")))
.andExpect(jsonPath(toWorkDeweyItemNumber(workBase), equalTo("item number")))
.andExpect(jsonPath(toWorkDeweyEditionNumber(workBase), equalTo("edition number")))
.andExpect(jsonPath(toWorkDeweyEdition(workBase), equalTo("edition")))
.andExpect(jsonPath(toDeweyAssigningSourceId(workBase), containsInAnyOrder("11")))
.andExpect(jsonPath(toDeweyAssigningSourceLabel(workBase), containsInAnyOrder("assigning agency")))
.andExpect(jsonPath(toWorkCreatorId(workBase), containsInAnyOrder("1001", "1002", "1003", "1004")))
.andExpect(jsonPath(toWorkCreatorLabel(workBase), containsInAnyOrder("name-MEETING", "name-PERSON",
"name-ORGANIZATION", "name-FAMILY")))
Expand Down Expand Up @@ -1041,14 +1050,14 @@ private void validateWork(Resource work, boolean validateFullInstance) {
validateParallelTitle(outgoingEdgeIterator.next(), work);
validateWorkContentType(outgoingEdgeIterator.next(), work);
validateWorkTargetAudience(outgoingEdgeIterator.next(), work);
validateWorkClassification(outgoingEdgeIterator.next(), work);
validateWorkGovernmentPublication(outgoingEdgeIterator.next(), work);
validateWorkContributor(outgoingEdgeIterator.next(), work, ORGANIZATION, CREATOR.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, ORGANIZATION, EDITOR.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, ORGANIZATION, CONTRIBUTOR.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, ORGANIZATION, ASSIGNEE.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, FAMILY, CREATOR.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, FAMILY, CONTRIBUTOR.getUri());
validateWorkClassification(outgoingEdgeIterator.next(), work);
validateBasicTitle(outgoingEdgeIterator.next(), work);
validateWorkContributor(outgoingEdgeIterator.next(), work, PERSON, AUTHOR.getUri());
validateWorkContributor(outgoingEdgeIterator.next(), work, PERSON, CREATOR.getUri());
Expand Down Expand Up @@ -1082,11 +1091,19 @@ private void validateWorkClassification(ResourceEdge edge, Resource source) {
assertThat(edge.getSource()).isEqualTo(source);
assertThat(edge.getPredicate().getUri()).isEqualTo(CLASSIFICATION.getUri());
var classification = edge.getTarget();
assertThat(classification.getDoc().size()).isEqualTo(2);
assertThat(classification.getDoc().get(CODE.getValue()).size()).isEqualTo(1);
assertThat(classification.getDoc().get(CODE.getValue()).get(0).asText()).isEqualTo("709.83");
assertThat(classification.getDoc().get(SOURCE.getValue()).size()).isEqualTo(1);
assertThat(classification.getDoc().get(SOURCE.getValue()).get(0).asText()).isEqualTo("ddc");
var types = classification.getTypes().stream().map(ResourceTypeEntity::getUri).toList();
assertThat(types).contains(ResourceTypeDictionary.CLASSIFICATION.getUri());
assertThat(classification.getDoc().size()).isEqualTo(5);
validateLiteral(classification, CODE.getValue(), "709.83");
validateLiteral(classification, SOURCE.getValue(), "ddc");
validateLiteral(classification, ITEM_NUMBER.getValue(), "item number");
validateLiteral(classification, EDITION_NUMBER.getValue(), "edition number");
validateLiteral(classification, EDITION.getValue(), "edition");
var resourceEdge = classification.getOutgoingEdges().iterator().next();
var assigningSource = resourceEdge.getTarget();
validateResourceEdge(resourceEdge, classification, assigningSource, PredicateDictionary.ASSIGNING_SOURCE.getUri());
assertThat(assigningSource.getDoc().size()).isZero();
assertThat(assigningSource.getLabel()).isEqualTo("assigning agency");
}

private void validateWorkContentType(ResourceEdge edge, Resource source) {
Expand Down Expand Up @@ -1219,7 +1236,8 @@ private LookupResources saveLookupResources() {
List.of(unitedStates, europe),
List.of(genre1, genre2),
List.of(creatorMeeting, creatorPerson, creatorOrganization, creatorFamily,
contributorPerson, contributorMeeting, contributorOrganization, contributorFamily)
contributorPerson, contributorMeeting, contributorOrganization, contributorFamily),
List.of(saveResource(11L, "assigning agency", ORGANIZATION, "{}"))
);
}

Expand Down Expand Up @@ -1545,6 +1563,28 @@ private String toWorkDeweyCode(String workBase) {
return join(".", workBase, arrayPath(CLASSIFICATION.getUri()), arrayPath(CODE.getValue()));
}

private String toWorkDeweyItemNumber(String workBase) {
return join(".", workBase, arrayPath(CLASSIFICATION.getUri()), arrayPath(ITEM_NUMBER.getValue()));
}

private String toWorkDeweyEditionNumber(String workBase) {
return join(".", workBase, arrayPath(CLASSIFICATION.getUri()), arrayPath(EDITION_NUMBER.getValue()));
}

private String toWorkDeweyEdition(String workBase) {
return join(".", workBase, arrayPath(CLASSIFICATION.getUri()), arrayPath(EDITION.getValue()));
}

private String toDeweyAssigningSourceId(String workBase) {
return join(".", join(".", workBase, arrayPath(CLASSIFICATION.getUri())),
dynamicArrayPath(ASSIGNING_SOURCE_REF), path(ID_PROPERTY));
}

private String toDeweyAssigningSourceLabel(String workBase) {
return join(".", join(".", workBase, arrayPath(CLASSIFICATION.getUri())),
dynamicArrayPath(ASSIGNING_SOURCE_REF), path(LABEL_PROPERTY));
}

private String toWorkCreatorId(String workBase) {
return join(".", workBase, dynamicArrayPath(CREATOR_REF), path(ID_PROPERTY));
}
Expand Down Expand Up @@ -1665,7 +1705,8 @@ private record LookupResources(
List<Resource> subjects,
List<Resource> geographicCoverages,
List<Resource> genres,
List<Resource> creators
List<Resource> creators,
List<Resource> assigningSources
) {
}
}
Loading

0 comments on commit 3e9162d

Please sign in to comment.