Skip to content

Commit

Permalink
MODLD-535: Disable automatic transformation of MARC bibliographic rec…
Browse files Browse the repository at this point in the history
…ords (#7)
  • Loading branch information
askhat-abishev authored Nov 4, 2024
1 parent b05c139 commit e7717b3
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 434 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.folio.linked.data.domain.dto.ResourceRequestDto;
import org.folio.linked.data.domain.dto.ResourceResponseDto;
import org.folio.linked.data.rest.resource.ResourceApi;
import org.folio.linked.data.service.resource.ResourceMarcService;
import org.folio.linked.data.service.resource.ResourceMarcBibService;
import org.folio.linked.data.service.resource.ResourceService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -19,7 +19,7 @@
public class ResourceController implements ResourceApi {

private final ResourceService resourceService;
private final ResourceMarcService resourceMarcService;
private final ResourceMarcBibService resourceMarcService;

@Override
public ResponseEntity<ResourceResponseDto> createResource(String okapiTenant, @Valid ResourceRequestDto resourceDto) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.folio.linked.data.integration.kafka.listener;

import static java.util.Optional.ofNullable;
import static org.folio.linked.data.domain.dto.SourceRecordType.MARC_AUTHORITY;
import static org.folio.linked.data.util.Constants.FOLIO_PROFILE;
import static org.folio.linked.data.util.KafkaUtils.getHeaderValueByName;
import static org.folio.spring.integration.XOkapiHeaders.TENANT;
Expand Down Expand Up @@ -56,11 +57,13 @@ private void processRecord(ConsumerRecord<String, SourceRecordDomainEvent> consu
.map(SourceRecordType::fromValue)
.orElseThrow();

tenantScopedExecutionService.executeAsyncWithRetry(
consumerRecord.headers(),
retryContext -> runRetryableJob(event, sourceRecordType, retryContext),
ex -> logFailedEvent(event, sourceRecordType, ex, false)
);
if (sourceRecordType == MARC_AUTHORITY) {
tenantScopedExecutionService.executeAsyncWithRetry(
consumerRecord.headers(),
retryContext -> runRetryableJob(event, sourceRecordType, retryContext),
ex -> logFailedEvent(event, sourceRecordType, ex, false)
);
}
}

private void runRetryableJob(SourceRecordDomainEvent event, SourceRecordType type, RetryContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package org.folio.linked.data.integration.kafka.listener.handler;

import static java.util.Objects.isNull;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE;
import static org.folio.linked.data.domain.dto.SourceRecordDomainEvent.EventTypeEnum.CREATED;
import static org.folio.linked.data.domain.dto.SourceRecordDomainEvent.EventTypeEnum.UPDATED;
import static org.folio.linked.data.domain.dto.SourceRecordType.MARC_AUTHORITY;
import static org.folio.linked.data.domain.dto.SourceRecordType.MARC_BIB;
import static org.folio.linked.data.model.entity.ResourceSource.LINKED_DATA;
import static org.folio.linked.data.util.Constants.FOLIO_PROFILE;

import java.util.Set;
Expand All @@ -17,11 +14,8 @@
import org.folio.ld.dictionary.model.Resource;
import org.folio.linked.data.domain.dto.SourceRecordDomainEvent;
import org.folio.linked.data.domain.dto.SourceRecordType;
import org.folio.linked.data.model.entity.FolioMetadata;
import org.folio.linked.data.repo.FolioMetadataRepository;
import org.folio.linked.data.service.resource.ResourceMarcService;
import org.folio.linked.data.service.resource.ResourceMarcAuthorityService;
import org.folio.marc4ld.service.marc2ld.authority.MarcAuthority2ldMapper;
import org.folio.marc4ld.service.marc2ld.bib.MarcBib2ldMapper;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

Expand All @@ -35,23 +29,23 @@ public class SourceRecordDomainEventHandler {
private static final String EMPTY_RESOURCE_MAPPED = "Empty resource(s) mapped from SourceRecordDomainEvent [id {}]";
private static final String NO_MARC_EVENT = "SourceRecordDomainEvent [id {}] has no Marc record inside";
private static final String UNSUPPORTED_TYPE = "Ignoring unsupported {} type [{}] in SourceRecordDomainEvent [id {}]";
private static final String IGNORED_LINKED_DATA_SOURCE = "Instance [id {}] has source = LINKED_DATA, "
+ "thus skipping it's update out of SourceRecordDomainEvent [id {}]";
private static final Set<SourceRecordType> SUPPORTED_RECORD_TYPES = Set.of(MARC_BIB, MARC_AUTHORITY);
private static final Set<SourceRecordDomainEvent.EventTypeEnum> SUPPORTED_EVENT_TYPES = Set.of(CREATED, UPDATED);
private final MarcBib2ldMapper marcBib2ldMapper;
private final MarcAuthority2ldMapper marcAuthority2ldMapper;
private final ResourceMarcService resourceMarcService;
private final FolioMetadataRepository folioMetadataRepository;
private final ResourceMarcAuthorityService resourceMarcService;

@SuppressWarnings("java:S125")
public void handle(SourceRecordDomainEvent event, SourceRecordType recordType) {
if (notProcessableEvent(event, recordType)) {
return;
}
if (recordType == MARC_BIB) {
saveBib(event);
} else if (recordType == MARC_AUTHORITY) {
if (recordType == MARC_AUTHORITY) {
saveAuthorities(event);
// } else if (recordType == MARC_BIB) {
// For now, you can discard the event if it is a MARC_BIB record
// In coming sprints, we will have stories to update the hrid (extracted out of MARC field 001) in DB for marc
// bib records
// this.updateHrId(event);
}
}

Expand All @@ -73,43 +67,19 @@ private boolean notProcessableEvent(SourceRecordDomainEvent event, SourceRecordT
return false;
}

private void saveBib(SourceRecordDomainEvent event) {
marcBib2ldMapper.fromMarcJson(event.getEventPayload().getParsedRecord().getContent())
.ifPresentOrElse(resource -> {
if (isInstanceWithLinkedDataSource(resource)) {
log.info(IGNORED_LINKED_DATA_SOURCE, resource.getId(), event.getId());
} else {
saveResource(resource, event, MARC_BIB);
}
},
() -> logEmptyResource(event.getId())
);
}

private boolean isInstanceWithLinkedDataSource(Resource resource) {
return resource.getTypes().equals(Set.of(INSTANCE))
&& ofNullable(resource.getFolioMetadata())
.map(org.folio.ld.dictionary.model.FolioMetadata::getSrsId)
.flatMap(folioMetadataRepository::findBySrsId)
.or(() -> folioMetadataRepository.findById(resource.getId()))
.map(FolioMetadata::getSource)
.map(LINKED_DATA::equals)
.orElse(false);
}

private void saveAuthorities(SourceRecordDomainEvent event) {
var mapped = marcAuthority2ldMapper.fromMarcJson(event.getEventPayload().getParsedRecord().getContent());
if (mapped.isEmpty()) {
logEmptyResource(event.getId());
} else {
mapped.forEach(resource -> saveResource(resource, event, MARC_AUTHORITY));
mapped.forEach(resource -> saveAuthority(resource, event));
}
}

private void saveResource(Resource resource, SourceRecordDomainEvent event, SourceRecordType recordType) {
private void saveAuthority(Resource resource, SourceRecordDomainEvent event) {
if (CREATED == event.getEventType() || UPDATED == event.getEventType()) {
var id = resourceMarcService.saveMarcResource(resource);
log.info(EVENT_SAVED, event.getId(), recordType.getValue(), id);
log.info(EVENT_SAVED, event.getId(), MARC_AUTHORITY, id);
}
}

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

import org.folio.ld.dictionary.model.Resource;

public interface ResourceMarcAuthorityService {

Long saveMarcResource(Resource modelResource);

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package org.folio.linked.data.service.resource;

import org.folio.ld.dictionary.model.Resource;
import org.folio.linked.data.domain.dto.ResourceIdDto;
import org.folio.linked.data.domain.dto.ResourceMarcViewDto;
import org.folio.linked.data.domain.dto.ResourceResponseDto;

public interface ResourceMarcService {

Long saveMarcResource(Resource modelResource);
public interface ResourceMarcBibService {

ResourceMarcViewDto getResourceMarcView(Long id);

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

import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
Expand All @@ -17,6 +17,7 @@
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.repo.ResourceEdgeRepository;
import org.folio.linked.data.repo.ResourceRepository;
import org.folio.linked.data.service.resource.ResourceGraphService;
import org.folio.linked.data.util.JsonUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.folio.linked.data.service.resource.impl;

import static java.util.Objects.isNull;
import static org.folio.ld.dictionary.PredicateDictionary.REPLACED_BY;
import static org.folio.ld.dictionary.PropertyDictionary.RESOURCE_PREFERRED;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.linked.data.exception.NotFoundException;
import org.folio.linked.data.mapper.ResourceModelMapper;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.model.entity.event.ResourceCreatedEvent;
import org.folio.linked.data.model.entity.event.ResourceEvent;
import org.folio.linked.data.model.entity.event.ResourceReplacedEvent;
import org.folio.linked.data.model.entity.event.ResourceUpdatedEvent;
import org.folio.linked.data.repo.FolioMetadataRepository;
import org.folio.linked.data.repo.ResourceRepository;
import org.folio.linked.data.service.resource.ResourceGraphService;
import org.folio.linked.data.service.resource.ResourceMarcAuthorityService;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Log4j2
@Service
@Transactional
@RequiredArgsConstructor
public class ResourceMarcAuthorityServiceImpl implements ResourceMarcAuthorityService {

private final ObjectMapper objectMapper;
private final ResourceRepository resourceRepo;
private final ResourceModelMapper resourceModelMapper;
private final ResourceGraphService resourceGraphService;
private final FolioMetadataRepository folioMetadataRepository;
private final ApplicationEventPublisher applicationEventPublisher;

@Override
public Long saveMarcResource(org.folio.ld.dictionary.model.Resource modelResource) {
var mapped = resourceModelMapper.toEntity(modelResource);
if (!mapped.isAuthority()) {
var message = "Resource is not an authority";
log.error(message);
throw new IllegalArgumentException(message);
}
if (resourceRepo.existsById(modelResource.getId())) {
return updateAuthority(mapped);
}
if (folioMetadataRepository.existsBySrsId(modelResource.getFolioMetadata().getSrsId())) {
return replaceAuthority(mapped);
}
return createAuthority(mapped);
}

private Long updateAuthority(Resource resource) {
var id = resource.getId();
var srsId = resource.getFolioMetadata().getSrsId();
logMarcAction(resource, "found by id [" + id + "] with srsId [" + srsId + "]", "be updated");
return saveAndPublishEvent(resource, ResourceUpdatedEvent::new);
}

private Long replaceAuthority(Resource resource) {
var srsId = resource.getFolioMetadata().getSrsId();
return resourceRepo.findByFolioMetadataSrsId(srsId)
.map(previous -> {
var previousObsolete = markObsolete(previous);
setPreferred(resource, true);
var re = new ResourceEdge(previousObsolete, resource, REPLACED_BY);
previousObsolete.addOutgoingEdge(re);
resource.addIncomingEdge(re);
logMarcAction(resource, "not found by id, but found by srsId [" + srsId + "]",
"be saved as a new version of previously existed resource [id " + previous.getId() + "]");
return saveAndPublishEvent(resource, saved -> new ResourceReplacedEvent(previousObsolete, saved));
})
.orElseThrow(() -> new NotFoundException("Resource not found by srsId: " + srsId));
}

private Long createAuthority(Resource resource) {
logMarcAction(resource, "not found by id and srsId", "be created");
return saveAndPublishEvent(resource, ResourceCreatedEvent::new);
}

private void logMarcAction(Resource resource, String existence, String action) {
log.info("Incoming Authority resource [id {}, srsId {}] is {} and will {}",
resource.getId(), resource.getFolioMetadata().getSrsId(), existence, action);
}

private Long saveAndPublishEvent(Resource resource, Function<Resource, ResourceEvent> resourceEventSupplier) {
var newResource = resourceGraphService.saveMergingGraph(resource);
var event = resourceEventSupplier.apply(newResource);
if (event instanceof ResourceReplacedEvent rre) {
resourceRepo.save(rre.previous());
}
applicationEventPublisher.publishEvent(event);
return newResource.getId();
}

private Resource markObsolete(Resource resource) {
resource.setActive(false);
setPreferred(resource, false);
resource.setFolioMetadata(null);
return resource;
}

private void setPreferred(Resource resource, boolean preferred) {
if (isNull(resource.getDoc())) {
resource.setDoc(objectMapper.createObjectNode());
}
var arrayNode = objectMapper.createArrayNode().add(String.valueOf(preferred));
((ObjectNode) resource.getDoc()).set(RESOURCE_PREFERRED.getValue(), arrayNode);
}
}
Loading

0 comments on commit e7717b3

Please sign in to comment.