Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MODLD-643: Preservation of MARC fields that are not rendered in the UI #94

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.model.entity.FolioMetadata;
import org.folio.linked.data.model.entity.PredicateEntity;
import org.folio.linked.data.model.entity.RawMarc;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceTypeEntity;
import org.mapstruct.BeforeMapping;
Expand All @@ -36,6 +37,8 @@ public Resource toEntity(org.folio.ld.dictionary.model.Resource model) {

@Mapping(target = "folioMetadata",
expression = "java(model.getFolioMetadata() != null ? mapFolioMetadata(model.getFolioMetadata(), resource) : null)")
@Mapping(target = "unmappedMarc",
expression = "java(model.getUnmappedMarc() != null ? mapUnmappedMarc(model.getUnmappedMarc(), resource) : null)")
protected abstract Resource toEntity(org.folio.ld.dictionary.model.Resource model,
@Context CyclicGraphContext cycleContext);

Expand Down Expand Up @@ -63,6 +66,9 @@ protected PredicateDictionary map(PredicateEntity predicateEntity) {
protected abstract FolioMetadata mapFolioMetadata(org.folio.ld.dictionary.model.FolioMetadata folioMetadata,
Resource resource);

@Mapping(source = "resource", target = "resource")
protected abstract RawMarc mapUnmappedMarc(org.folio.ld.dictionary.model.RawMarc unmappedMarc, Resource resource);

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/org/folio/linked/data/model/entity/RawMarc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.folio.linked.data.model.entity;

import static lombok.AccessLevel.PROTECTED;

import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.hibernate.annotations.Type;

@Entity
@Data
@NoArgsConstructor(access = PROTECTED)
@Accessors(chain = true)
@Table(name = "raw_marcs")
@EqualsAndHashCode(of = "id")
public class RawMarc {

@Id
@Column(name = "resource_hash")
private Long id;

@Column(columnDefinition = "json")
@Type(JsonBinaryType.class)
private String content;

@OneToOne
@MapsId
@JoinColumn(name = "resource_hash")
@ToString.Exclude
private Resource resource;

public RawMarc(Resource resource) {
this.resource = resource;
this.id = resource.getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ public class Resource implements Persistable<Long> {
@PrimaryKeyJoinColumn
private FolioMetadata folioMetadata;

@OneToOne(cascade = ALL, mappedBy = "resource", orphanRemoval = true)
@PrimaryKeyJoinColumn
private RawMarc unmappedMarc;

@Column(name = "created_date", updatable = false, nullable = false)
private Timestamp createdDate;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.folio.linked.data.service.resource.edge;

import static org.folio.ld.dictionary.PredicateDictionary.ADMIN_METADATA;
import static org.folio.ld.dictionary.PredicateDictionary.DISSERTATION;
import static org.folio.ld.dictionary.PredicateDictionary.GENRE;
import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS;
import static org.folio.ld.dictionary.PredicateDictionary.SUPPLEMENTARY_CONTENT;
import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE;
import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK;

import java.util.Map;
import java.util.Set;
Expand All @@ -23,18 +28,21 @@
public class ResourceEdgeServiceImpl implements ResourceEdgeService {

private static final Map<ResourceTypeDictionary, Set<PredicateDictionary>> EDGES_TO_BE_COPIED = Map.of(
INSTANCE, Set.of(ADMIN_METADATA)
INSTANCE, Set.of(ADMIN_METADATA),
WORK, Set.of(ILLUSTRATIONS, SUPPLEMENTARY_CONTENT, DISSERTATION, GENRE)
);
private final ResourceRepository resourceRepository;
private final ResourceModelMapper resourceModelMapper;
private final ResourceEdgeRepository resourceEdgeRepository;

@Override
public void copyOutgoingEdges(Resource from, Resource to) {
getEdgesToBeCopied(from)
.stream()
.map(edge -> new ResourceEdge(to, edge.getTarget(), edge.getPredicate()))
.forEach(to::addOutgoingEdge);
if (from.getTypes().equals(to.getTypes())) {
getEdgesToBeCopied(from)
.stream()
.map(edge -> new ResourceEdge(to, edge.getTarget(), edge.getPredicate()))
.forEach(to::addOutgoingEdge);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<include file="tables/type_lookup.sql" relativeToChangelogFile="true"/>
<include file="tables/resources.sql" relativeToChangelogFile="true"/>
<include file="tables/folio_metadata.sql" relativeToChangelogFile="true"/>
<include file="tables/raw_marcs.sql" relativeToChangelogFile="true"/>
<include file="tables/resource_edges.sql" relativeToChangelogFile="true"/>
<include file="tables/resource_type_map.sql" relativeToChangelogFile="true"/>
<include file="tables/resource_context_map.sql" relativeToChangelogFile="true"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
create table if not exists raw_marcs (
resource_hash bigint primary key references resources(resource_hash),
content jsonb null
);

comment on table raw_marcs is 'Store unmapped MARC records associated with an Instance resource. Applicable only for Instance resources';
comment on column raw_marcs.resource_hash is 'The unique hash identifier for the resource';
comment on column raw_marcs.content is 'JSON representation of MARC record';
Original file line number Diff line number Diff line change
Expand Up @@ -618,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(59);
assertThat(resourceTestService.countEdges()).isEqualTo(61);
assertThat(resourceTestService.countResources()).isEqualTo(61);
assertThat(resourceTestService.countEdges()).isEqualTo(63);
var requestBuilder = delete(RESOURCE_URL + "/" + instance.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env));
Expand All @@ -630,9 +630,9 @@ void deleteResourceById_shouldDeleteRootInstanceAndRootEdges_reindexWork() throw
// then
resultActions.andExpect(status().isNoContent());
assertThat(resourceTestService.existsById(instance.getId())).isFalse();
assertThat(resourceTestService.countResources()).isEqualTo(58);
assertThat(resourceTestService.countResources()).isEqualTo(60);
assertThat(resourceTestService.findEdgeById(instance.getOutgoingEdges().iterator().next().getId())).isNotPresent();
assertThat(resourceTestService.countEdges()).isEqualTo(43);
assertThat(resourceTestService.countEdges()).isEqualTo(45);
checkSearchIndexMessage(work.getId(), UPDATE);
checkIndexDate(work.getId().toString());
}
Expand All @@ -642,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(59);
assertThat(resourceTestService.countEdges()).isEqualTo(61);
assertThat(resourceTestService.countResources()).isEqualTo(61);
assertThat(resourceTestService.countEdges()).isEqualTo(63);
var requestBuilder = delete(RESOURCE_URL + "/" + existed.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env));
Expand All @@ -654,9 +654,9 @@ void deleteResourceById_shouldDeleteRootWorkAndRootEdges() throws Exception {
// then
resultActions.andExpect(status().isNoContent());
assertThat(resourceTestService.existsById(existed.getId())).isFalse();
assertThat(resourceTestService.countResources()).isEqualTo(58);
assertThat(resourceTestService.countResources()).isEqualTo(60);
assertThat(resourceTestService.findEdgeById(existed.getOutgoingEdges().iterator().next().getId())).isNotPresent();
assertThat(resourceTestService.countEdges()).isEqualTo(30);
assertThat(resourceTestService.countEdges()).isEqualTo(32);
checkSearchIndexMessage(existed.getId(), DELETE);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package org.folio.linked.data.service.resource.edge;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.ld.dictionary.PredicateDictionary.ADMIN_METADATA;
import static org.folio.ld.dictionary.PredicateDictionary.DISSERTATION;
import static org.folio.ld.dictionary.PredicateDictionary.GENRE;
import static org.folio.ld.dictionary.PredicateDictionary.ILLUSTRATIONS;
import static org.folio.ld.dictionary.PredicateDictionary.SUPPLEMENTARY_CONTENT;
import static org.folio.ld.dictionary.PredicateDictionary.TITLE;
import static org.folio.linked.data.test.TestUtil.randomLong;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import org.folio.ld.dictionary.model.Resource;
import org.folio.ld.dictionary.model.ResourceEdge;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.mapper.ResourceModelMapper;
import org.folio.linked.data.model.entity.PredicateEntity;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.model.entity.ResourceTypeEntity;
import org.folio.linked.data.repo.ResourceEdgeRepository;
import org.folio.linked.data.repo.ResourceRepository;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
Expand All @@ -37,7 +51,9 @@ class ResourceEdgeServiceTest {
void saveNewResourceEdge_shouldSaveMappedEdgeResourceWithReferenceToSource() {
// given
var sourceId = randomLong();
var edgeModel = new ResourceEdge(new Resource().setId(sourceId), new Resource().setId(randomLong()), TITLE);
var edgeModel = new org.folio.ld.dictionary.model.ResourceEdge(
new org.folio.ld.dictionary.model.Resource().setId(sourceId),
new org.folio.ld.dictionary.model.Resource().setId(randomLong()), TITLE);
var mappedEdgeResource = new org.folio.linked.data.model.entity.Resource().setId(edgeModel.getTarget().getId());
doReturn(mappedEdgeResource).when(resourceModelMapper).toEntity(edgeModel.getTarget());
doReturn(mappedEdgeResource).when(resourceRepository).save(mappedEdgeResource);
Expand All @@ -53,4 +69,59 @@ void saveNewResourceEdge_shouldSaveMappedEdgeResourceWithReferenceToSource() {
assertThat(result.getPredicateHash()).isEqualTo(edgeModel.getPredicate().getHash());
}

static Stream<Arguments> dataProvider() {
return Stream.of(
Arguments.of(getInstance(), getEmptyInstance(), List.of(ADMIN_METADATA.getUri())),
Arguments.of(getWork(), getEmptyWork(), List.of(ILLUSTRATIONS.getUri(), SUPPLEMENTARY_CONTENT.getUri(),
DISSERTATION.getUri(), GENRE.getUri())),
Arguments.of(getInstance(), getEmptyWork(), List.of()),
Arguments.of(getWork(), getEmptyInstance(), List.of())
);
}

@ParameterizedTest
@MethodSource("dataProvider")
void copyOutgoingEdges_shouldCopy_appropriateEdges(Resource from,
Resource to,
List<String> expectedPredicates) {
//when
resourceEdgeService.copyOutgoingEdges(from, to);

//then
assertThat(to.getOutgoingEdges()
.stream()
.map(ResourceEdge::getPredicate)
.map(PredicateEntity::getUri)
.toList()
).containsExactlyInAnyOrderElementsOf(expectedPredicates);
}

private static Resource getEmptyInstance() {
var instance = new Resource();
instance.setTypes(Set.of(new ResourceTypeEntity(1L, ResourceTypeDictionary.INSTANCE.getUri(), "instance")));
return instance;
}

private static Resource getInstance() {
var instance = getEmptyInstance();
instance.addOutgoingEdge(new ResourceEdge(instance, new Resource(), TITLE));
instance.addOutgoingEdge(new ResourceEdge(instance, new Resource(), ADMIN_METADATA));
return instance;
}

private static Resource getEmptyWork() {
var work = new Resource();
work.setTypes(Set.of(new ResourceTypeEntity(2L, ResourceTypeDictionary.WORK.getUri(), "work")));
return work;
}

private static Resource getWork() {
var work = getEmptyWork();
work.addOutgoingEdge(new ResourceEdge(work, new Resource(), TITLE));
work.addOutgoingEdge(new ResourceEdge(work, new Resource(), ILLUSTRATIONS));
work.addOutgoingEdge(new ResourceEdge(work, new Resource(), SUPPLEMENTARY_CONTENT));
work.addOutgoingEdge(new ResourceEdge(work, new Resource(), DISSERTATION));
work.addOutgoingEdge(new ResourceEdge(work, new Resource(), GENRE));
return work;
}
}
68 changes: 46 additions & 22 deletions src/test/java/org/folio/linked/data/test/MonographTestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -523,26 +523,6 @@ 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 supplementaryContent = createResource(
Map.of(
CODE, List.of("code"),
TERM, List.of("supplementary content term"),
LINK, List.of("http://id.loc.gov/vocabulary/msupplcont/code")
),
Set.of(CATEGORY),
emptyMap()
).setLabel("supplementary content term");

var pred2OutgoingResources = new LinkedHashMap<PredicateDictionary, List<Resource>>();
pred2OutgoingResources.put(TITLE, List.of(primaryTitle, createParallelTitle(), createVariantTitle()));
pred2OutgoingResources.put(CLASSIFICATION, List.of(createLcClassification(), createDeweyClassification()));
Expand All @@ -561,8 +541,8 @@ 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));
pred2OutgoingResources.put(PredicateDictionary.SUPPLEMENTARY_CONTENT, List.of(supplementaryContent));
pred2OutgoingResources.put(ILLUSTRATIONS, List.of(createIllustrations()));
pred2OutgoingResources.put(PredicateDictionary.SUPPLEMENTARY_CONTENT, List.of(createSupplementaryContent()));

var work = createResource(
Map.ofEntries(
Expand Down Expand Up @@ -712,6 +692,50 @@ private static Resource createTargetAudience() {
).setLabel("Primary");
}

private static Resource createIllustrations() {
var categorySet = createResource(
Map.of(
LINK, List.of("http://id.loc.gov/vocabulary/millus"),
LABEL, List.of("Illustrative Content")
),
Set.of(CATEGORY_SET),
emptyMap())
.setLabel("Illustrative Content");
var pred2OutgoingResources = new LinkedHashMap<PredicateDictionary, List<Resource>>();
pred2OutgoingResources.put(IS_DEFINED_BY, List.of(categorySet));
return 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),
pred2OutgoingResources
).setLabel("illustrations term");
}

private static Resource createSupplementaryContent() {
var categorySet = createResource(
Map.of(
LINK, List.of("http://id.loc.gov/vocabulary/msupplcont"),
LABEL, List.of("Supplementary Content")
),
Set.of(CATEGORY_SET),
emptyMap())
.setLabel("Supplementary Content");
var pred2OutgoingResources = new LinkedHashMap<PredicateDictionary, List<Resource>>();
pred2OutgoingResources.put(IS_DEFINED_BY, List.of(categorySet));
return createResource(
Map.of(
CODE, List.of("code"),
TERM, List.of("supplementary content term"),
LINK, List.of("http://id.loc.gov/vocabulary/msupplcont/code")
),
Set.of(CATEGORY),
pred2OutgoingResources
).setLabel("supplementary content term");
}

private Resource status(String prefix) {
return createResource(
Map.of(
Expand Down