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-642: Retain edges when resource is fetched from DB #93

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
11 changes: 0 additions & 11 deletions src/main/java/org/folio/linked/data/model/entity/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,6 @@ public Resource(@NonNull Resource that) {
.orElse(null);
}

public static Resource copyWithNoEdges(@NonNull Resource that) {
return new Resource()
.setId(that.id)
.setLabel(that.label)
.setDoc((JsonNode) ofNullable(that.getDoc()).map(JsonNode::deepCopy).orElse(null))
.setIndexDate(that.indexDate)
.setTypes(new LinkedHashSet<>(that.getTypes()))
.setIncomingEdges(new LinkedHashSet<>())
.setOutgoingEdges(new LinkedHashSet<>());
}

@Override
public boolean isNew() {
return !managed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ private Optional<Resource> fetchResourceFromRepo(Identifiable identifiable) {
.flatMap(id -> resourceRepo.findById(parseLong(id)))
.map(ResourceUtils::ensureLatestReplaced)
.or(() -> Optional.ofNullable(identifiable.getSrsId())
.flatMap(resourceRepo::findByFolioMetadataSrsId))
.map(Resource::copyWithNoEdges);
.flatMap(resourceRepo::findByFolioMetadataSrsId));
}

private Resource createResourceFromSrs(String srsId) {
Expand All @@ -83,7 +82,6 @@ private Resource createResourceFromSrs(String srsId) {
.flatMap(this::contentAsJsonString)
.flatMap(this::firstAuthorityToEntity)
.map(resourceGraphService::saveMergingGraph)
.map(Resource::copyWithNoEdges)
Copy link
Contributor Author

@pkjacob pkjacob Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @AndreiBordak, Do you remember why .map(Resource::copyWithNoEdges) was added here (also in line 77)? Do you see any reason for that?

.orElseThrow(() -> notFoundException(srsId));
} catch (FeignException.NotFound e) {
throw notFoundException(srsId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package org.folio.linked.data.e2e.resource;

import static org.folio.linked.data.e2e.resource.ResourceControllerITBase.RESOURCE_URL;
import static org.folio.linked.data.test.TestUtil.awaitAndAssert;
import static org.folio.linked.data.test.TestUtil.defaultHeaders;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.folio.ld.dictionary.PredicateDictionary;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.domain.dto.InstanceIngressEvent;
import org.folio.linked.data.e2e.base.IntegrationTest;
import org.folio.linked.data.model.entity.FolioMetadata;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.service.resource.hash.HashService;
import org.folio.linked.data.test.kafka.KafkaInventoryTopicListener;
import org.folio.linked.data.test.kafka.KafkaProducerTestConfiguration;
import org.folio.linked.data.test.resource.ResourceTestService;
import org.folio.marc4ld.service.marc2ld.reader.MarcReaderProcessor;
import org.junit.jupiter.api.Test;
import org.marc4j.marc.DataField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.web.servlet.MockMvc;

@IntegrationTest
@SpringBootTest(classes = {KafkaProducerTestConfiguration.class})
class ResourceControllerUpdateWorkIT {
@Autowired
private ResourceTestService resourceTestService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private HashService hashService;
@Autowired
private Environment env;
@Autowired
private MockMvc mockMvc;
@Autowired
private KafkaInventoryTopicListener inventoryTopicListener;
@Autowired
private MarcReaderProcessor marcReader;

@Test
void updateWork_should_send_update_instance_event_to_inventory() throws Exception {
// given
var person = getPerson();
resourceTestService.saveGraph(person);
var work = getWork();
var instance = getInstance(work);
resourceTestService.saveGraph(instance);

// when
var workUpdateRequestDto = getWorkRequestDto(person.getId(), instance.getId());

var updateRequest = put(RESOURCE_URL + "/" + work.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env))
.content(workUpdateRequestDto);

mockMvc.perform(updateRequest).andExpect(status().isOk());

// then
awaitAndAssert(() ->
assertTrue(inventoryTopicListener.getMessages().stream()
.anyMatch(m -> isExpectedEvent(m, instance.getId()))
)
);
}

private String getWorkRequestDto(Long personId, Long instanceId) {
return """
{
"resource": {
"http://bibfra.me/vocab/lite/Work": {
"http://bibfra.me/vocab/marc/title": [
{
"http://bibfra.me/vocab/marc/Title": {
"http://bibfra.me/vocab/marc/mainTitle": [ "simple_work" ]
}
}
],
"_creatorReference": [ { "id": "%PERSON_ID%" } ],
"_instanceReference": [ { "id": "%INSTANCE_ID%"} ]
}
}
}
"""
.replace("%PERSON_ID%", personId.toString())
.replace("%INSTANCE_ID%", instanceId.toString());
}

private Resource getWork() {
var title = new Resource()
.addTypes(ResourceTypeDictionary.TITLE)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/marc/mainTitle": ["simple_work"]
}
"""))
.setLabel("simple_work");
var work = new Resource()
.addTypes(ResourceTypeDictionary.WORK)
.setDoc(getDoc("{}"))
.setLabel("simple_work");

work.addOutgoingEdge(new ResourceEdge(work, title, PredicateDictionary.TITLE));

title.setId(hashService.hash(title));
work.setId(hashService.hash(work));

return work;
}

private Resource getInstance(Resource work) {
var title = new Resource()
.addTypes(ResourceTypeDictionary.TITLE)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/marc/mainTitle": ["simple_instance"]
}
"""))
.setLabel("simple_work");
var instance = new Resource()
.addTypes(ResourceTypeDictionary.INSTANCE)
.setDoc(getDoc("{}"))
.setLabel("simple_instance");

instance.addOutgoingEdge(new ResourceEdge(instance, title, PredicateDictionary.TITLE));
instance.addOutgoingEdge(new ResourceEdge(instance, work, PredicateDictionary.INSTANTIATES));

title.setId(hashService.hash(title));
instance.setId(hashService.hash(instance));

return instance;
}

private Resource getPerson() {
var person = new Resource()
.addTypes(ResourceTypeDictionary.PERSON)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/lite/name": ["Person name"]
}
"""))
.setLabel("Person name");

var lccn = new Resource()
.addTypes(ResourceTypeDictionary.ID_LCCN, ResourceTypeDictionary.IDENTIFIER)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/lite/link": ["n123456789"]
}
"""))
.setLabel("n123456789");

person.setFolioMetadata(new FolioMetadata(person).setInventoryId("123456789"));
person.addOutgoingEdge(new ResourceEdge(person, lccn, PredicateDictionary.MAP));

person.setId(hashService.hash(person));
lccn.setId(hashService.hash(lccn));

return person;
}

@SneakyThrows
private JsonNode getDoc(String doc) {
return objectMapper.readTree(doc);
}

@SneakyThrows
private <T> T parse(String json, Class<T> clazz) {
return objectMapper.readValue(json, clazz);
}

private boolean isExpectedEvent(String eventStr, long linkedDataId) {
var event = parse(eventStr, InstanceIngressEvent.class);
var eventPayload = event.getEventPayload();
var marc = eventPayload.getSourceRecordObject();
return event.getEventType() == InstanceIngressEvent.EventTypeEnum.UPDATE_INSTANCE
&& eventPayload.getAdditionalProperties().get("linkedDataId").equals(linkedDataId)
&& isExpectedMarc(marc);
}

private boolean isExpectedMarc(String marcStr) {
var marc = marcReader.readMarc(marcStr).toList().get(0);
var df100 = (DataField) marc.getVariableField("100");
var df245 = (DataField) marc.getVariableField("245");
var isDf100Valid = df100.getSubfields().stream().anyMatch(
sf ->
sf.getCode() == 'a' && sf.getData().equals("Person name")
|| sf.getCode() == '0' && sf.getData().equals("n123456789")
|| sf.getCode() == '9' && sf.getData().equals("123456789")
);
var isDf245Valid = df245.getSubfields().stream().anyMatch(
sf -> sf.getCode() == 'a' && sf.getData().equals("simple_instance")
);
return isDf100Valid && isDf245Valid;
}
}