From 22d573d87d09d16a328aa0b3652cb959a207534e Mon Sep 17 00:00:00 2001 From: Pavel Bobylev <131748689+PBobylev@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:26:45 +0500 Subject: [PATCH] MODLD-452: consume SRS domain event for create and update ops (#314) * MODLD-452: consume SRS domain event for create and update ops * MODLD-452: split resourceService and add more logs to ResourceMarcService * MODLD-452: service packages structure optimization --- docker-compose.yml | 121 +++++--- .../linked/data/client/SearchClient.java | 4 +- .../json/ObjectMapperConfig.java | 3 - .../event/DataImportEventDeserializer.java | 92 ------ .../kafka/KafkaListenerConfiguration.java | 16 +- .../kafka/KafkaProducerConfiguration.java | 8 +- .../data/controller/ReindexingController.java | 2 +- .../data/controller/ResourceController.java | 6 +- .../controller/ResourceGraphController.java | 6 +- .../consumer/DataImportEventHandler.java | 54 ---- .../kafka/listener/KafkaMessageListener.java | 77 ----- .../SourceRecordDomainEventListener.java | 83 ++++++ .../SourceRecordDomainEventHandler.java | 122 ++++++++ .../monograph/common/CategoryMapperUnit.java | 2 +- .../monograph/common/StatusMapperUnit.java | 2 +- .../common/place/OriginPlaceMapperUnit.java | 2 +- .../common/place/PlaceMapperUnit.java | 2 +- .../common/place/ProviderPlaceMapperUnit.java | 2 +- .../common/title/ParallelTitleMapperUnit.java | 2 +- .../common/title/PrimaryTitleMapperUnit.java | 2 +- .../common/title/VariantTitleMapperUnit.java | 2 +- .../instance/InstanceMapperUnit.java | 2 +- .../sub/AccessLocationMapperUnit.java | 2 +- .../instance/sub/CarrierMapperUnit.java | 2 +- .../sub/CopyrightEventMapperUnit.java | 2 +- .../instance/sub/MediaMapperUnit.java | 2 +- .../sub/SupplementaryContentMapperUnit.java | 2 +- .../sub/identified/EanMapperUnit.java | 2 +- .../sub/identified/IsbnMapperUnit.java | 2 +- .../sub/identified/LccnMapperUnit.java | 2 +- .../sub/identified/LocalIdMapperUnit.java | 2 +- .../sub/identified/OtherIdMapperUnit.java | 2 +- .../sub/provision/DistributionMapperUnit.java | 2 +- .../sub/provision/ManufactureMapperUnit.java | 2 +- .../sub/provision/ProductionMapperUnit.java | 2 +- .../provision/ProviderEventMapperUnit.java | 2 +- .../sub/provision/PublicationMapperUnit.java | 2 +- .../dto/monograph/work/WorkMapperUnit.java | 2 +- .../work/sub/ClassificationMapperUnit.java | 2 +- .../monograph/work/sub/ContentMapperUnit.java | 2 +- .../work/sub/DissertationMapperUnit.java | 2 +- .../sub/GovernmentPublicationMapperUnit.java | 2 +- .../work/sub/LanguageMapperUnit.java | 2 +- .../work/sub/TargetAudienceMapperUnit.java | 2 +- .../InstanceIngressMessageMapper.java | 4 +- .../search/AuthoritySearchMessageMapper.java | 10 +- .../BibliographicSearchMessageMapper.java | 62 ++-- ...thorityIdentifiersInnerMapperAbstract.java | 27 -- ...esInnerIdentifiersInnerMapperAbstract.java | 28 -- ...thorityIdentifiersInnerMapperAbstract.java | 27 ++ ...esInnerIdentifiersInnerMapperAbstract.java | 30 ++ .../entity/event/ResourceCreatedEvent.java | 2 +- .../entity/event/ResourceDeletedEvent.java | 2 +- .../model/entity/event/ResourceEvent.java | 4 + .../entity/event/ResourceIndexedEvent.java | 2 +- .../entity/event/ResourceReplacedEvent.java | 2 +- .../entity/event/ResourceUpdatedEvent.java | 2 +- .../data/repo/InstanceMetadataRepository.java | 4 +- .../linked/data/repo/ResourceRepository.java | 3 + .../{impl => }/DictionaryServiceImpl.java | 3 +- .../{impl => }/ProfileServiceImpl.java | 3 +- .../service/impl/ResourceServiceImpl.java | 278 ------------------ .../{ => index}/BatchIndexService.java | 2 +- .../BatchIndexServiceImpl.java | 3 +- .../service/{ => index}/ReindexService.java | 2 +- .../{impl => index}/ReindexServiceImpl.java | 6 +- .../resource/ResourceGraphService.java | 15 + .../resource/ResourceGraphServiceImpl.java | 118 ++++++++ .../service/resource/ResourceMarcService.java | 11 + .../resource/ResourceMarcServiceImpl.java | 145 +++++++++ .../{ => resource}/ResourceService.java | 12 +- .../service/resource/ResourceServiceImpl.java | 130 ++++++++ .../{ => resource/hash}/HashService.java | 2 +- .../hash}/HashServiceImpl.java | 3 +- .../tenant/LinkedTenantService.java | 3 +- .../tenant/TenantScopedExecutionService.java | 2 +- .../worker}/DictionaryWorker.java | 3 +- .../worker}/KafkaAdminWorker.java | 3 +- .../worker}/SearchWorker.java | 7 +- .../worker}/TenantServiceWorker.java | 2 +- .../folio/linked/data/util/KafkaUtils.java | 16 + src/main/resources/application-folio.yml | 8 +- .../swagger.api/events/dataImportEvent.json | 27 -- .../resources/swagger.api/folio-modules.yaml | 28 +- .../inventory/instanceIngressEvent.json | 0 .../inventory/instanceIngressPayload.json | 0 .../{ => folio-modules}/inventory/uuid.json | 0 .../search/createIndexRequest.json | 0 .../search/createIndexResponse.json} | 0 .../search/indexDataKafkaMessage.json | 0 .../search/linkedDataAuthority.json | 0 .../search/linkedDataWork.json | 0 .../search/linkedDataWorkIndexTitleType.json | 0 .../search/resourceIndexEvent.json | 0 .../search/resourceIndexEventType.json | 0 .../srs/sourceRecordDomainEvent.json | 89 ++++++ .../folio-modules/srs/sourceRecordType.json | 12 + .../DataImportEventDeserializerTest.java | 90 ------ .../controller/ResourceControllerTest.java | 7 +- .../linked/data/e2e/MergeResourcesIT.java | 18 +- .../data/e2e/ResourceControllerFolioIT.java | 2 +- .../linked/data/e2e/ResourceControllerIT.java | 2 +- .../integration/KafkaMessageListenerIT.java | 98 ------ .../listener/KafkaMessageListenerIT.java | 77 +++++ .../SourceRecordDomainEventHandlerIT.java} | 175 ++++++----- .../SourceRecordDomainEventHandlerTest.java | 174 +++++++++++ .../place/OriginPlaceMapperUnitTest.java | 2 +- .../work/sub/CarrierMapperUnitTest.java | 2 +- .../work/sub/ContentMapperUnitTest.java | 2 +- .../GovernmentPublicationMapperUnitTest.java | 2 +- .../work/sub/MediaMapperUnitTest.java | 2 +- .../sub/TargetAudienceMapperUnitTest.java | 2 +- .../InstanceIngressMessageMapperTest.java | 3 +- .../AuthoritySearchMessageMapperTest.java | 11 +- .../BibliographicSearchMessageMapperTest.java | 48 +-- .../{impl => }/DictionaryServiceImplTest.java | 2 +- .../{impl => }/ProfileServiceImplTest.java | 2 +- .../BatchIndexServiceImplTest.java | 3 +- .../{ => index}/ReindexServiceTest.java | 6 +- .../resource/ResourceGraphServiceTest.java | 69 +++++ .../resource/ResourceMarcServiceTest.java | 178 +++++++++++ .../{ => resource}/ResourceServiceTest.java | 133 ++------- .../{ => resource/hash}/HashServiceTest.java | 3 +- .../tenant/LinkedTenantServiceTest.java | 3 +- .../worker}/DictionaryWorkerTest.java | 2 +- .../worker}/KafkaAdminWorkerTest.java | 2 +- .../worker}/SearchWorkerTest.java | 2 +- .../data/test/ResourceTestRepository.java | 21 ++ .../linked/data/test/ResourceTestService.java | 6 +- .../org/folio/linked/data/test/TestUtil.java | 1 + .../kafka/KafkaEventsTestDataFixture.java | 67 ++--- .../linked/data/util/KafkaUtilsTest.java | 47 +++ src/test/resources/application-test-folio.yml | 2 +- .../samples/{ => marc2ld}/authority_100.jsonl | 0 .../marc2ld}/expected_message.json | 0 .../{ => marc2ld}/full_marc_sample.jsonl | 0 .../{ => marc2ld}/marc_monograph_leader.jsonl | 0 .../marc_non_monograph_leader.jsonl | 0 .../samples/marc2ld/small_instance.jsonl | 59 ++++ .../samples/marc2ld/small_instance_upd.jsonl | 39 +++ 140 files changed, 1914 insertions(+), 1248 deletions(-) delete mode 100644 src/main/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializer.java delete mode 100644 src/main/java/org/folio/linked/data/integration/kafka/consumer/DataImportEventHandler.java delete mode 100644 src/main/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListener.java create mode 100644 src/main/java/org/folio/linked/data/integration/kafka/listener/SourceRecordDomainEventListener.java create mode 100644 src/main/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandler.java delete mode 100644 src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeAuthorityIdentifiersInnerMapperAbstract.java delete mode 100644 src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeInstancesInnerIdentifiersInnerMapperAbstract.java create mode 100644 src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataAuthorityIdentifiersInnerMapperAbstract.java create mode 100644 src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract.java create mode 100644 src/main/java/org/folio/linked/data/model/entity/event/ResourceEvent.java rename src/main/java/org/folio/linked/data/service/{impl => }/DictionaryServiceImpl.java (93%) rename src/main/java/org/folio/linked/data/service/{impl => }/ProfileServiceImpl.java (87%) delete mode 100644 src/main/java/org/folio/linked/data/service/impl/ResourceServiceImpl.java rename src/main/java/org/folio/linked/data/service/{ => index}/BatchIndexService.java (85%) rename src/main/java/org/folio/linked/data/service/{impl => index}/BatchIndexServiceImpl.java (94%) rename src/main/java/org/folio/linked/data/service/{ => index}/ReindexService.java (62%) rename src/main/java/org/folio/linked/data/service/{impl => index}/ReindexServiceImpl.java (90%) create mode 100644 src/main/java/org/folio/linked/data/service/resource/ResourceGraphService.java create mode 100644 src/main/java/org/folio/linked/data/service/resource/ResourceGraphServiceImpl.java create mode 100644 src/main/java/org/folio/linked/data/service/resource/ResourceMarcService.java create mode 100644 src/main/java/org/folio/linked/data/service/resource/ResourceMarcServiceImpl.java rename src/main/java/org/folio/linked/data/service/{ => resource}/ResourceService.java (63%) create mode 100644 src/main/java/org/folio/linked/data/service/resource/ResourceServiceImpl.java rename src/main/java/org/folio/linked/data/service/{ => resource/hash}/HashService.java (74%) rename src/main/java/org/folio/linked/data/service/{impl => resource/hash}/HashServiceImpl.java (87%) rename src/main/java/org/folio/linked/data/service/{impl => }/tenant/LinkedTenantService.java (93%) rename src/main/java/org/folio/linked/data/service/{impl => }/tenant/TenantScopedExecutionService.java (98%) rename src/main/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/DictionaryWorker.java (84%) rename src/main/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/KafkaAdminWorker.java (84%) rename src/main/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/SearchWorker.java (81%) rename src/main/java/org/folio/linked/data/service/{impl/tenant => tenant/worker}/TenantServiceWorker.java (86%) create mode 100644 src/main/java/org/folio/linked/data/util/KafkaUtils.java delete mode 100644 src/main/resources/swagger.api/events/dataImportEvent.json rename src/main/resources/swagger.api/{ => folio-modules}/inventory/instanceIngressEvent.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/inventory/instanceIngressPayload.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/inventory/uuid.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/createIndexRequest.json (100%) rename src/main/resources/swagger.api/{search/folioCreateIndexResponse.json => folio-modules/search/createIndexResponse.json} (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/indexDataKafkaMessage.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/linkedDataAuthority.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/linkedDataWork.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/linkedDataWorkIndexTitleType.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/resourceIndexEvent.json (100%) rename src/main/resources/swagger.api/{ => folio-modules}/search/resourceIndexEventType.json (100%) create mode 100644 src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json create mode 100644 src/main/resources/swagger.api/folio-modules/srs/sourceRecordType.json delete mode 100644 src/test/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializerTest.java delete mode 100644 src/test/java/org/folio/linked/data/integration/KafkaMessageListenerIT.java create mode 100644 src/test/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListenerIT.java rename src/test/java/org/folio/linked/data/integration/{DataImportEventListenerIT.java => kafka/listener/handler/SourceRecordDomainEventHandlerIT.java} (62%) create mode 100644 src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerTest.java rename src/test/java/org/folio/linked/data/service/{impl => }/DictionaryServiceImplTest.java (98%) rename src/test/java/org/folio/linked/data/service/{impl => }/ProfileServiceImplTest.java (97%) rename src/test/java/org/folio/linked/data/service/{impl => index}/BatchIndexServiceImplTest.java (95%) rename src/test/java/org/folio/linked/data/service/{ => index}/ReindexServiceTest.java (93%) create mode 100644 src/test/java/org/folio/linked/data/service/resource/ResourceGraphServiceTest.java create mode 100644 src/test/java/org/folio/linked/data/service/resource/ResourceMarcServiceTest.java rename src/test/java/org/folio/linked/data/service/{ => resource}/ResourceServiceTest.java (75%) rename src/test/java/org/folio/linked/data/service/{ => resource/hash}/HashServiceTest.java (94%) rename src/test/java/org/folio/linked/data/service/{impl => }/tenant/LinkedTenantServiceTest.java (93%) rename src/test/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/DictionaryWorkerTest.java (93%) rename src/test/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/KafkaAdminWorkerTest.java (94%) rename src/test/java/org/folio/linked/data/service/{impl/tenant/workers => tenant/worker}/SearchWorkerTest.java (94%) create mode 100644 src/test/java/org/folio/linked/data/test/ResourceTestRepository.java create mode 100644 src/test/java/org/folio/linked/data/util/KafkaUtilsTest.java rename src/test/resources/samples/{ => marc2ld}/authority_100.jsonl (100%) rename src/test/resources/{integration/kafka/search => samples/marc2ld}/expected_message.json (100%) rename src/test/resources/samples/{ => marc2ld}/full_marc_sample.jsonl (100%) rename src/test/resources/samples/{ => marc2ld}/marc_monograph_leader.jsonl (100%) rename src/test/resources/samples/{ => marc2ld}/marc_non_monograph_leader.jsonl (100%) create mode 100644 src/test/resources/samples/marc2ld/small_instance.jsonl create mode 100644 src/test/resources/samples/marc2ld/small_instance_upd.jsonl diff --git a/docker-compose.yml b/docker-compose.yml index 78ad05a2..9d02ab3b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,44 +1,34 @@ -version: '3.8' services: - db: - container_name: postgres - image: postgres:12-alpine - restart: always - environment: - - POSTGRES_DB=okapi_modules - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - ports: - - '5432:5432' - volumes: - - db:/var/lib/postgresql/data zookeeper: - image: confluentinc/cp-zookeeper:7.3.2 + image: bitnami/zookeeper container_name: zookeeper - hostname: zookeeper - ports: - - "2181:2181" environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 + ALLOW_ANONYMOUS_LOGIN: "yes" + ports: + - 2181:2181 + + kafka: - image: confluentinc/cp-kafka:7.3.2 + image: bitnami/kafka container_name: kafka - hostname: kafka - ports: - - "9092:9092" - - "9997:9997" depends_on: - zookeeper + ports: + - 29092:29092 + - 9092:9092 environment: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_JMX_PORT: 9997 + KAFKA_CFG_LISTENERS: INTERNAL://:9092,LOCAL://:29092 + KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://host.docker.internal:9092,LOCAL://localhost:29092 + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: LOCAL:PLAINTEXT,INTERNAL:PLAINTEXT + KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true" + KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_CFG_NODE_ID: 1 + KAFKA_CFG_LOG_RETENTION_BYTES: -1 + KAFKA_CFG_LOG_RETENTION_HOURS: -1 + kafka-ui: container_name: kafka-ui image: provectuslabs/kafka-ui:latest @@ -53,6 +43,71 @@ services: KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 KAFKA_CLUSTERS_0_JMXPORT: 9997 DYNAMIC_CONFIG_ENABLED: 'true' -volumes: - db: - driver: local + + postgres: + image: postgres:13 + container_name: postgres + mem_limit: 2g + environment: + POSTGRES_PASSWORD: folio_admin + POSTGRES_USER: folio_admin + POSTGRES_DB: okapi_modules + command: -c max_connections=200 -c shared_buffers=512MB -c log_duration=on -c log_min_duration_statement=0ms -c shared_preload_libraries=pg_stat_statements -c jit=off + ports: + - 5432:5432 + + expose-docker-on-2375: + image: alpine/socat + container_name: expose-docker-on-2375 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: "tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock" + restart: always + + #minio: + # image: 'minio/minio' + # command: server /data --console-address ":9001" + # ports: + # - 9000:9000 + # - 9001:9001 + + #createbuckets: # This container will terminate after running its commands to create a bucket in minio + # image: minio/mc + # depends_on: + # - minio + # entrypoint: > + # /bin/sh -c " + # /usr/bin/mc config host add myminio http://host.docker.internal:9000 minioadmin minioadmin; + # /usr/bin/mc rm -r --force myminio/example-bucket; + # /usr/bin/mc mb myminio/example-bucket; + # exit 0; + # " + + #okapi: + # image: 'folioci/okapi:latest' + # command: 'dev' + # ports: + # - 9130:9130 + # environment: # be careful to leave a space character after every java option + # JAVA_OPTIONS: |- + # -Dhttp.port=9130 + # -Dokapiurl=http://host.docker.internal:9130 + # -Dstorage=postgres + # -Dpostgres_username=folio_admin + # -Dpostgres_password=folio_admin + # -Dpostgres_database=okapi_modules + # -Dpostgres_host=host.docker.internal + # -Dhost=host.docker.internal + # -Dport_end=9170 + # -DdockerUrl=tcp://expose-docker-on-2375:2375 + # depends_on: + # - postgres + + #elasticsearch: + # image: 'ghcr.io/zcube/bitnami-compat/elasticsearch:7.17.9' + # ports: + # - 9300:9300 + # - 9200:9200 + # environment: + # ELASTICSEARCH_PLUGINS: + # "analysis-icu,analysis-kuromoji,analysis-smartcn,analysis-nori,analysis-phonetic" diff --git a/src/main/java/org/folio/linked/data/client/SearchClient.java b/src/main/java/org/folio/linked/data/client/SearchClient.java index cc60c9d2..58a51dae 100644 --- a/src/main/java/org/folio/linked/data/client/SearchClient.java +++ b/src/main/java/org/folio/linked/data/client/SearchClient.java @@ -4,7 +4,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import org.folio.search.domain.dto.CreateIndexRequest; -import org.folio.search.domain.dto.FolioCreateIndexResponse; +import org.folio.search.domain.dto.CreateIndexResponse; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; @@ -16,6 +16,6 @@ public interface SearchClient { @PostMapping(value = "/index/indices", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) - ResponseEntity createIndex(@RequestBody CreateIndexRequest createIndexRequest); + ResponseEntity createIndex(@RequestBody CreateIndexRequest createIndexRequest); } diff --git a/src/main/java/org/folio/linked/data/configuration/json/ObjectMapperConfig.java b/src/main/java/org/folio/linked/data/configuration/json/ObjectMapperConfig.java index 6eab9af9..7d66e16b 100644 --- a/src/main/java/org/folio/linked/data/configuration/json/ObjectMapperConfig.java +++ b/src/main/java/org/folio/linked/data/configuration/json/ObjectMapperConfig.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.folio.linked.data.configuration.json.deserialization.ResourceRequestFieldDeserializer; -import org.folio.linked.data.configuration.json.deserialization.event.DataImportEventDeserializer; import org.folio.linked.data.configuration.json.deserialization.instance.InstanceRequestAllOfMapDeserializer; import org.folio.linked.data.configuration.json.deserialization.title.TitleFieldRequestDeserializer; import org.folio.linked.data.configuration.json.serialization.MarcRecordSerializationConfig; @@ -14,7 +13,6 @@ import org.folio.linked.data.domain.dto.MarcRecord; import org.folio.linked.data.domain.dto.ResourceRequestField; import org.folio.linked.data.domain.dto.TitleFieldRequest; -import org.folio.search.domain.dto.DataImportEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -40,7 +38,6 @@ private Module monographModule(ObjectMapper mapper) { module.addDeserializer(ResourceRequestField.class, new ResourceRequestFieldDeserializer()); module.addDeserializer(TitleFieldRequest.class, new TitleFieldRequestDeserializer()); module.addDeserializer(InstanceRequestAllOfMap.class, new InstanceRequestAllOfMapDeserializer()); - module.addDeserializer(DataImportEvent.class, new DataImportEventDeserializer(mapper)); return module; } } diff --git a/src/main/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializer.java b/src/main/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializer.java deleted file mode 100644 index ad66c312..00000000 --- a/src/main/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializer.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.folio.linked.data.configuration.json.deserialization.event; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; -import org.folio.search.domain.dto.DataImportEvent; - -/** - * Class responsible for deserializing the Kafka event from Data Import module into {@link DataImportEvent} object. - */ -@Log4j2 -@RequiredArgsConstructor -public class DataImportEventDeserializer extends JsonDeserializer { - - private static final String ID_FILED = "id"; - private static final String EVENT_PAYLOAD_FIELD = "eventPayload"; - private static final String EVENT_TYPE_FIELD = "eventType"; - private static final String CONTEXT_FIELD = "context"; - private static final String MARC_BIBLIOGRAPHIC_FIELD = "MARC_BIBLIOGRAPHIC"; - private static final String PARSED_RECORD_FIELD = "parsedRecord"; - private static final String CONTENT_FIELD = "content"; - private static final String TENANT_FIELD = "tenant"; - private static final String DI_INVENTORY_INSTANCE_CREATED = "DI_INVENTORY_INSTANCE_CREATED"; - private static final String CURRENT_EVENT_TYPE = "CURRENT_EVENT_TYPE"; - private static final String MARC_AUTHORITY = "MARC_AUTHORITY"; - - private final ObjectMapper objectMapper; - - @Override - public DataImportEvent deserialize(JsonParser jp, DeserializationContext context) throws IOException { - DataImportEvent event = new DataImportEvent(); - - JsonNode node = jp.getCodec().readTree(jp); - - if (node.has(ID_FILED)) { - event.setId(node.get(ID_FILED).textValue()); - } - - if (node.has(EVENT_PAYLOAD_FIELD)) { - JsonNode eventPayload = objectMapper.readTree(node.get(EVENT_PAYLOAD_FIELD).textValue()); - event.setEventType(eventPayload.path(EVENT_TYPE_FIELD).textValue()); - event.setTenant(eventPayload.path(TENANT_FIELD).textValue()); - if (isInstanceCreatedEvent(eventPayload)) { - getMarcBibRecord(eventPayload).ifPresent(event::setMarcBib); - } else if (isAuthorityEvent(eventPayload)) { - getMarcAuthorityRecord(eventPayload).ifPresent(event::setMarcAuthority); - } - } - - return event; - } - - private Optional getMarcBibRecord(JsonNode eventPayload) { - return getMarc(eventPayload, MARC_BIBLIOGRAPHIC_FIELD); - } - - private Optional getMarcAuthorityRecord(JsonNode eventPayload) { - return getMarc(eventPayload, MARC_AUTHORITY); - } - - @SneakyThrows - private Optional getMarc(JsonNode eventPayload, String contextField) { - var marcWrapper = eventPayload.path(CONTEXT_FIELD).path(contextField).textValue(); - if (isBlank(marcWrapper)) { - return Optional.empty(); - } - var marc = objectMapper.readTree(marcWrapper).path(PARSED_RECORD_FIELD).path(CONTENT_FIELD).textValue(); - return Optional.ofNullable(marc); - } - - private boolean isInstanceCreatedEvent(JsonNode eventPayload) { - if (eventPayload.has(CONTEXT_FIELD)) { - var contextField = eventPayload.path(CONTEXT_FIELD); - return contextField.has(CURRENT_EVENT_TYPE) - && DI_INVENTORY_INSTANCE_CREATED.equals(contextField.path(CURRENT_EVENT_TYPE).textValue()); - } - return false; - } - - private boolean isAuthorityEvent(JsonNode eventPayload) { - return eventPayload.has(CONTEXT_FIELD) && eventPayload.path(CONTEXT_FIELD).has(MARC_AUTHORITY); - } -} diff --git a/src/main/java/org/folio/linked/data/configuration/kafka/KafkaListenerConfiguration.java b/src/main/java/org/folio/linked/data/configuration/kafka/KafkaListenerConfiguration.java index c429d7b1..81ee8b79 100644 --- a/src/main/java/org/folio/linked/data/configuration/kafka/KafkaListenerConfiguration.java +++ b/src/main/java/org/folio/linked/data/configuration/kafka/KafkaListenerConfiguration.java @@ -9,7 +9,7 @@ import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.kafka.common.serialization.StringDeserializer; -import org.folio.search.domain.dto.DataImportEvent; +import org.folio.search.domain.dto.SourceRecordDomainEvent; import org.folio.spring.tools.kafka.FolioKafkaProperties; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -28,7 +28,7 @@ public class KafkaListenerConfiguration { private final KafkaProperties kafkaProperties; - private final ObjectMapper objectMapper; + private final ObjectMapper mapper; @Bean @ConfigurationProperties("folio.kafka") @@ -37,16 +37,18 @@ public FolioKafkaProperties folioKafkaProperties() { } @Bean - public ConcurrentKafkaListenerContainerFactory dataImportListenerContainerFactory() { - var factory = new ConcurrentKafkaListenerContainerFactory(); + public ConcurrentKafkaListenerContainerFactory srsEventListenerContainerFactory( + ConsumerFactory sourceRecordDomainEventConsumerFactory + ) { + var factory = new ConcurrentKafkaListenerContainerFactory(); factory.setBatchListener(true); - factory.setConsumerFactory(dataImportEventConsumerFactory()); + factory.setConsumerFactory(sourceRecordDomainEventConsumerFactory); return factory; } @Bean - public ConsumerFactory dataImportEventConsumerFactory() { - var deserializer = new ErrorHandlingDeserializer<>(new JsonDeserializer<>(DataImportEvent.class, objectMapper)); + public ConsumerFactory sourceRecordDomainEventConsumerFactory() { + var deserializer = new ErrorHandlingDeserializer<>(new JsonDeserializer<>(SourceRecordDomainEvent.class, mapper)); Map config = new HashMap<>(kafkaProperties.buildConsumerProperties(null)); config.put(KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); config.put(VALUE_DESERIALIZER_CLASS_CONFIG, deserializer); diff --git a/src/main/java/org/folio/linked/data/configuration/kafka/KafkaProducerConfiguration.java b/src/main/java/org/folio/linked/data/configuration/kafka/KafkaProducerConfiguration.java index 67bd5c80..a347af15 100644 --- a/src/main/java/org/folio/linked/data/configuration/kafka/KafkaProducerConfiguration.java +++ b/src/main/java/org/folio/linked/data/configuration/kafka/KafkaProducerConfiguration.java @@ -9,6 +9,8 @@ import lombok.RequiredArgsConstructor; import org.apache.kafka.common.serialization.StringSerializer; import org.folio.search.domain.dto.InstanceIngressEvent; +import org.folio.search.domain.dto.LinkedDataAuthority; +import org.folio.search.domain.dto.LinkedDataWork; import org.folio.search.domain.dto.ResourceIndexEvent; import org.folio.spring.tools.kafka.FolioMessageProducer; import org.jetbrains.annotations.NotNull; @@ -34,7 +36,7 @@ public FolioMessageProducer bibliographicMessageProducer( ) { var producer = new FolioMessageProducer<>(resourceIndexEventMessageTemplate, linkedDataTopicProperties::getWorkSearchIndex); - producer.setKeyMapper(ResourceIndexEvent::getId); + producer.setKeyMapper(rie -> ((LinkedDataWork) rie.getNew()).getId()); return producer; } @@ -44,7 +46,7 @@ public FolioMessageProducer authorityMessageProducer( ) { var producer = new FolioMessageProducer<>(resourceIndexEventMessageTemplate, linkedDataTopicProperties::getAuthoritySearchIndex); - producer.setKeyMapper(ResourceIndexEvent::getId); + producer.setKeyMapper(rie -> ((LinkedDataAuthority) rie.getNew()).getId()); return producer; } @@ -54,7 +56,7 @@ public FolioMessageProducer instanceIngressEventProducer( ) { var producer = new FolioMessageProducer<>(instanceIngressMessageTemplate, linkedDataTopicProperties::getInstanceIngress); - producer.setKeyMapper(InstanceIngressEvent::getId); + producer.setKeyMapper(iie -> iie.getEventPayload().getSourceRecordIdentifier()); return producer; } diff --git a/src/main/java/org/folio/linked/data/controller/ReindexingController.java b/src/main/java/org/folio/linked/data/controller/ReindexingController.java index 128c7b27..46ec223b 100644 --- a/src/main/java/org/folio/linked/data/controller/ReindexingController.java +++ b/src/main/java/org/folio/linked/data/controller/ReindexingController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.folio.linked.data.rest.resource.ReindexApi; -import org.folio.linked.data.service.ReindexService; +import org.folio.linked.data.service.index.ReindexService; import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; diff --git a/src/main/java/org/folio/linked/data/controller/ResourceController.java b/src/main/java/org/folio/linked/data/controller/ResourceController.java index 639fc6b2..57dcc6e4 100644 --- a/src/main/java/org/folio/linked/data/controller/ResourceController.java +++ b/src/main/java/org/folio/linked/data/controller/ResourceController.java @@ -8,7 +8,8 @@ import org.folio.linked.data.domain.dto.ResourceResponseDto; import org.folio.linked.data.domain.dto.ResourceShortInfoPage; import org.folio.linked.data.rest.resource.ResourceApi; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceMarcService; +import org.folio.linked.data.service.resource.ResourceService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +18,7 @@ public class ResourceController implements ResourceApi { private final ResourceService resourceService; + private final ResourceMarcService resourceMarcService; @Override public ResponseEntity createResource(String okapiTenant, @Valid ResourceRequestDto resourceDto) { @@ -47,7 +49,7 @@ public ResponseEntity deleteResource(Long id, String okapiTenant) { @Override public ResponseEntity getResourceMarcViewById(Long id, String okapiTenant) { - return ResponseEntity.ok(resourceService.getResourceMarcViewById(id)); + return ResponseEntity.ok(resourceMarcService.getResourceMarcView(id)); } @Override diff --git a/src/main/java/org/folio/linked/data/controller/ResourceGraphController.java b/src/main/java/org/folio/linked/data/controller/ResourceGraphController.java index 700e646d..383346a3 100644 --- a/src/main/java/org/folio/linked/data/controller/ResourceGraphController.java +++ b/src/main/java/org/folio/linked/data/controller/ResourceGraphController.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.folio.linked.data.domain.dto.ResourceGraphDto; import org.folio.linked.data.rest.resource.GraphApi; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceGraphService; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @@ -13,10 +13,10 @@ @RequiredArgsConstructor public class ResourceGraphController implements GraphApi { - private final ResourceService resourceService; + private final ResourceGraphService resourceGraphService; @Override public ResponseEntity getResourceGraphById(Long id, String okapiTenant) { - return ResponseEntity.ok(resourceService.getResourceGraphById(id)); + return ResponseEntity.ok(resourceGraphService.getResourceGraph(id)); } } diff --git a/src/main/java/org/folio/linked/data/integration/kafka/consumer/DataImportEventHandler.java b/src/main/java/org/folio/linked/data/integration/kafka/consumer/DataImportEventHandler.java deleted file mode 100644 index 385c62e6..00000000 --- a/src/main/java/org/folio/linked/data/integration/kafka/consumer/DataImportEventHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.folio.linked.data.integration.kafka.consumer; - -import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; -import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.folio.linked.data.service.ResourceService; -import org.folio.marc4ld.service.marc2ld.authority.MarcAuthority2ldMapper; -import org.folio.marc4ld.service.marc2ld.bib.MarcBib2ldMapper; -import org.folio.search.domain.dto.DataImportEvent; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -@Log4j2 -@Component -@Profile(FOLIO_PROFILE) -@RequiredArgsConstructor -public class DataImportEventHandler { - - private final MarcBib2ldMapper marcBib2ldMapper; - private final MarcAuthority2ldMapper marcAuthority2ldMapper; - private final ResourceService resourceService; - - public void handle(DataImportEvent event) { - if (isNotEmpty(event.getMarcBib())) { - createBibResource(event); - } else if (isNotEmpty(event.getMarcAuthority())) { - createAuthorities(event); - } else { - log.error("DataImportEvent with id [{}], tenant [{}], eventType [{}] has no Marc record inside", - event.getId(), event.getTenant(), event.getEventType()); - } - } - - private void createBibResource(DataImportEvent event) { - marcBib2ldMapper.fromMarcJson(event.getMarcBib()) - .ifPresentOrElse(resource -> { - var id = resourceService.createResource(resource); - log.info("DataImportEvent with id [{}] was saved as LD resource with id [{}]", event.getId(), id); - }, - () -> log.warn("Empty resource from MARC for event ID [{}]", event.getId()) - ); - } - - private void createAuthorities(DataImportEvent event) { - var authorityResources = marcAuthority2ldMapper.fromMarcJson(event.getMarcAuthority()); - var ids = authorityResources.stream() - .map(resourceService::createResource) - .toList(); - log.info("Processing MARC Authority record with event ID [{}], saved records: {}", - event.getId(), ids.size()); - } -} diff --git a/src/main/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListener.java b/src/main/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListener.java deleted file mode 100644 index bef6d203..00000000 --- a/src/main/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListener.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.folio.linked.data.integration.kafka.listener; - -import static java.util.Optional.ofNullable; -import static org.apache.logging.log4j.util.Strings.isNotBlank; -import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; -import static org.folio.spring.integration.XOkapiHeaders.TENANT; -import static org.folio.spring.integration.XOkapiHeaders.URL; - -import java.util.List; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.common.header.Headers; -import org.apache.logging.log4j.Level; -import org.folio.linked.data.integration.kafka.consumer.DataImportEventHandler; -import org.folio.linked.data.service.impl.tenant.TenantScopedExecutionService; -import org.folio.search.domain.dto.DataImportEvent; -import org.springframework.context.annotation.Profile; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.retry.RetryContext; -import org.springframework.stereotype.Component; - -@Component -@Log4j2 -@Profile(FOLIO_PROFILE) -@RequiredArgsConstructor -public class KafkaMessageListener { - - private static final String DI_INSTANCE_CREATED_LISTENER = "mod-linked-data-data-import-instance-created-listener"; - private static final Set REQUIRED_FOLIO_HEADERS = Set.of(TENANT, URL); - private final TenantScopedExecutionService tenantScopedExecutionService; - private final DataImportEventHandler dataImportEventHandler; - - @KafkaListener( - id = DI_INSTANCE_CREATED_LISTENER, - containerFactory = "dataImportListenerContainerFactory", - groupId = "#{folioKafkaProperties.listener['data-import-instance-create'].groupId}", - concurrency = "#{folioKafkaProperties.listener['data-import-instance-create'].concurrency}", - topicPattern = "#{folioKafkaProperties.listener['data-import-instance-create'].topicPattern}") - public void handleDataImportInstanceCreatedEvent(List> consumerRecords) { - consumerRecords.forEach(this::processRecord); - } - - private void processRecord(ConsumerRecord consumerRecord) { - log.info("Received event: {}", consumerRecord); - var event = consumerRecord.value(); - if (isNotContainsRequiredHeaders(consumerRecord.headers())) { - log.warn("Received DataImportEvent with no required Folio headers will be ignored: {}", event); - return; - } - tenantScopedExecutionService.executeAsyncWithRetry( - consumerRecord.headers(), - retryContext -> runRetryableJob(event, retryContext), - ex -> logFailedEvent(event, ex, false) - ); - } - - private void runRetryableJob(DataImportEvent event, RetryContext context) { - ofNullable(context.getLastThrowable()) - .ifPresent(ex -> logFailedEvent(event, ex, true)); - dataImportEventHandler.handle(event); - } - - private void logFailedEvent(DataImportEvent event, Throwable ex, boolean isRetrying) { - String marcRecord = isNotBlank(event.getMarcBib()) ? event.getMarcBib() : event.getMarcAuthority(); - String type = isNotBlank(event.getMarcBib()) ? "Bib" : "Authority"; - var logLevel = isRetrying ? Level.INFO : Level.ERROR; - log.log(logLevel, "Failed to process MARC {} record {}. Retrying: {}", type, marcRecord, isRetrying, ex); - } - - private boolean isNotContainsRequiredHeaders(Headers headers) { - return !REQUIRED_FOLIO_HEADERS.stream() - .map(required -> headers.headers(required).iterator()) - .allMatch(iterator -> iterator.hasNext() && iterator.next().value().length > 0); - } -} diff --git a/src/main/java/org/folio/linked/data/integration/kafka/listener/SourceRecordDomainEventListener.java b/src/main/java/org/folio/linked/data/integration/kafka/listener/SourceRecordDomainEventListener.java new file mode 100644 index 00000000..e5ebd91f --- /dev/null +++ b/src/main/java/org/folio/linked/data/integration/kafka/listener/SourceRecordDomainEventListener.java @@ -0,0 +1,83 @@ +package org.folio.linked.data.integration.kafka.listener; + +import static java.util.Optional.ofNullable; +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; +import static org.folio.spring.integration.XOkapiHeaders.URL; + +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.Headers; +import org.apache.logging.log4j.Level; +import org.folio.linked.data.integration.kafka.listener.handler.SourceRecordDomainEventHandler; +import org.folio.linked.data.service.tenant.TenantScopedExecutionService; +import org.folio.search.domain.dto.SourceRecordDomainEvent; +import org.folio.search.domain.dto.SourceRecordType; +import org.springframework.context.annotation.Profile; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.retry.RetryContext; +import org.springframework.stereotype.Component; + +@Component +@Log4j2 +@Profile(FOLIO_PROFILE) +@RequiredArgsConstructor +public class SourceRecordDomainEventListener { + + private static final String SRS_DOMAIN_EVENT_LISTENER = "mod-linked-data-source-record-domain-event-listener"; + private static final String RECORD_TYPE = "folio.srs.recordType"; + private static final Set REQUIRED_HEADERS = Set.of(TENANT, URL, RECORD_TYPE); + private final TenantScopedExecutionService tenantScopedExecutionService; + private final SourceRecordDomainEventHandler sourceRecordDomainEventHandler; + + @KafkaListener( + id = SRS_DOMAIN_EVENT_LISTENER, + containerFactory = "srsEventListenerContainerFactory", + groupId = "#{folioKafkaProperties.listener['source-record-domain-event'].groupId}", + concurrency = "#{folioKafkaProperties.listener['source-record-domain-event'].concurrency}", + topicPattern = "#{folioKafkaProperties.listener['source-record-domain-event'].topicPattern}") + public void handleSourceRecordDomainEvent(List> consumerRecords) { + consumerRecords.forEach(this::processRecord); + } + + private void processRecord(ConsumerRecord consumerRecord) { + log.info("Received event [key {}]", consumerRecord.key()); + var event = consumerRecord.value(); + if (notAllRequiredHeaders(consumerRecord.headers())) { + log.warn("Received SourceRecordDomainEvent [id {}] will be ignored since not all required headers were provided", + event.getId()); + return; + } + var sourceRecordType = getHeaderValueByName(consumerRecord, RECORD_TYPE) + .map(SourceRecordType::fromValue) + .orElseThrow(); + + tenantScopedExecutionService.executeAsyncWithRetry( + consumerRecord.headers(), + retryContext -> runRetryableJob(event, sourceRecordType, retryContext), + ex -> logFailedEvent(event, sourceRecordType, ex, false) + ); + } + + private void runRetryableJob(SourceRecordDomainEvent event, SourceRecordType type, RetryContext context) { + ofNullable(context.getLastThrowable()) + .ifPresent(ex -> logFailedEvent(event, type, ex, true)); + sourceRecordDomainEventHandler.handle(event, type); + } + + private void logFailedEvent(SourceRecordDomainEvent event, SourceRecordType type, Throwable ex, boolean isRetrying) { + var marcRecord = event.getEventPayload(); + var logLevel = isRetrying ? Level.INFO : Level.ERROR; + log.log(logLevel, "Failed to process MARC {} record {}. Retrying: {}", type.name(), marcRecord, isRetrying, ex); + } + + private boolean notAllRequiredHeaders(Headers headers) { + return !REQUIRED_HEADERS.stream() + .map(required -> headers.headers(required).iterator()) + .allMatch(iterator -> iterator.hasNext() && iterator.next().value().length > 0); + } +} diff --git a/src/main/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandler.java b/src/main/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandler.java new file mode 100644 index 00000000..6a8d3bba --- /dev/null +++ b/src/main/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandler.java @@ -0,0 +1,122 @@ +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.model.entity.ResourceSource.LINKED_DATA; +import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum.CREATED; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum.UPDATED; +import static org.folio.search.domain.dto.SourceRecordType.MARC_AUTHORITY; +import static org.folio.search.domain.dto.SourceRecordType.MARC_BIB; + +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.ld.dictionary.model.Resource; +import org.folio.linked.data.model.entity.InstanceMetadata; +import org.folio.linked.data.repo.InstanceMetadataRepository; +import org.folio.linked.data.service.resource.ResourceMarcService; +import org.folio.marc4ld.service.marc2ld.authority.MarcAuthority2ldMapper; +import org.folio.marc4ld.service.marc2ld.bib.MarcBib2ldMapper; +import org.folio.search.domain.dto.SourceRecordDomainEvent; +import org.folio.search.domain.dto.SourceRecordType; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@Profile(FOLIO_PROFILE) +@RequiredArgsConstructor +public class SourceRecordDomainEventHandler { + + private static final String EVENT_SAVED = "SourceRecordDomainEvent [id {}] was saved as resource [id {}]"; + 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 SUPPORTED_RECORD_TYPES = Set.of(MARC_BIB, MARC_AUTHORITY); + private static final Set SUPPORTED_EVENT_TYPES = Set.of(CREATED, UPDATED); + private final MarcBib2ldMapper marcBib2ldMapper; + private final MarcAuthority2ldMapper marcAuthority2ldMapper; + private final ResourceMarcService resourceMarcService; + private final InstanceMetadataRepository instanceMetadataRepository; + + public void handle(SourceRecordDomainEvent event, SourceRecordType recordType) { + if (notProcessableEvent(event, recordType)) { + return; + } + if (recordType == MARC_BIB) { + saveBib(event); + } else if (recordType == MARC_AUTHORITY) { + saveAuthorities(event); + } + } + + private boolean notProcessableEvent(SourceRecordDomainEvent event, SourceRecordType recordType) { + if (isEmpty(event.getEventPayload())) { + log.warn(NO_MARC_EVENT, event.getId()); + return true; + } + if (isNull(recordType) || !SUPPORTED_RECORD_TYPES.contains(recordType)) { + logUnsupportedType(event, "record", recordType); + return true; + } + if (isNull(event.getEventType()) || !SUPPORTED_EVENT_TYPES.contains(event.getEventType())) { + logUnsupportedType(event, "event", event.getEventType()); + return true; + } + return false; + } + + private void saveBib(SourceRecordDomainEvent event) { + marcBib2ldMapper.fromMarcJson(event.getEventPayload()) + .ifPresentOrElse(resource -> { + if (isInstanceWithLinkedDataSource(resource)) { + log.info(IGNORED_LINKED_DATA_SOURCE, resource.getId(), event.getId()); + } else { + saveResource(resource, event); + } + }, + () -> logEmptyResource(event.getId()) + ); + } + + private boolean isInstanceWithLinkedDataSource(Resource resource) { + return resource.getTypes().equals(Set.of(INSTANCE)) + && ofNullable(resource.getInstanceMetadata()) + .map(org.folio.ld.dictionary.model.InstanceMetadata::getInventoryId) + .flatMap(instanceMetadataRepository::findByInventoryId) + .or(() -> instanceMetadataRepository.findById(resource.getId())) + .map(InstanceMetadata::getSource) + .map(LINKED_DATA::equals) + .orElse(false); + } + + private void saveAuthorities(SourceRecordDomainEvent event) { + var mapped = marcAuthority2ldMapper.fromMarcJson(event.getEventPayload()); + if (mapped.isEmpty()) { + logEmptyResource(event.getId()); + } else { + mapped.forEach(resource -> saveResource(resource, event)); + } + } + + private void saveResource(Resource resource, SourceRecordDomainEvent event) { + if (CREATED == event.getEventType() || UPDATED == event.getEventType()) { + var id = resourceMarcService.saveMarcResource(resource); + log.info(EVENT_SAVED, event.getId(), id); + } + } + + private void logUnsupportedType(SourceRecordDomainEvent event, String typeKind, Enum type) { + log.info(UNSUPPORTED_TYPE, typeKind, isNull(type) ? "null" : type.name(), event.getId()); + } + + private void logEmptyResource(String eventId) { + log.info(EMPTY_RESOURCE_MAPPED, eventId); + } + +} diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/CategoryMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/CategoryMapperUnit.java index 83b4d2e1..3e26f5b2 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/CategoryMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/CategoryMapperUnit.java @@ -26,7 +26,7 @@ import org.folio.linked.data.mapper.dto.common.SingleResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceEdge; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; @RequiredArgsConstructor public abstract class CategoryMapperUnit implements SingleResourceMapperUnit, MarcCodeProvider { diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/StatusMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/StatusMapperUnit.java index 42a4f12d..365eb11f 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/StatusMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/StatusMapperUnit.java @@ -28,7 +28,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.common.SingleResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnit.java index eca7de88..1dcec966 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnit.java @@ -14,7 +14,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/PlaceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/PlaceMapperUnit.java index 0450018a..2c658688 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/PlaceMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/PlaceMapperUnit.java @@ -21,7 +21,7 @@ import org.folio.linked.data.mapper.dto.common.SingleResourceMapperUnit; import org.folio.linked.data.mapper.dto.monograph.common.MarcCodeProvider; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; @RequiredArgsConstructor public abstract class PlaceMapperUnit implements SingleResourceMapperUnit, MarcCodeProvider { diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/ProviderPlaceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/ProviderPlaceMapperUnit.java index f5106116..3e02c49f 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/ProviderPlaceMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/place/ProviderPlaceMapperUnit.java @@ -14,7 +14,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/ParallelTitleMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/ParallelTitleMapperUnit.java index 4ee152da..840faef2 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/ParallelTitleMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/ParallelTitleMapperUnit.java @@ -24,7 +24,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/PrimaryTitleMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/PrimaryTitleMapperUnit.java index 97ebee4c..a76e8138 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/PrimaryTitleMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/PrimaryTitleMapperUnit.java @@ -23,7 +23,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/VariantTitleMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/VariantTitleMapperUnit.java index 59404274..53a96a68 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/VariantTitleMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/common/title/VariantTitleMapperUnit.java @@ -25,7 +25,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/InstanceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/InstanceMapperUnit.java index d2b48670..920b8303 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/InstanceMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/InstanceMapperUnit.java @@ -56,7 +56,7 @@ import org.folio.linked.data.mapper.dto.monograph.common.NoteMapper; import org.folio.linked.data.model.entity.InstanceMetadata; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/AccessLocationMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/AccessLocationMapperUnit.java index 6f0d62f8..8014e106 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/AccessLocationMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/AccessLocationMapperUnit.java @@ -17,7 +17,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CarrierMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CarrierMapperUnit.java index 37742d48..9798a6bc 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CarrierMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CarrierMapperUnit.java @@ -9,7 +9,7 @@ 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.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CopyrightEventMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CopyrightEventMapperUnit.java index de978c7e..30a00cc1 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CopyrightEventMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/CopyrightEventMapperUnit.java @@ -16,7 +16,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/MediaMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/MediaMapperUnit.java index 5c12d0b1..a64a0dd2 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/MediaMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/MediaMapperUnit.java @@ -9,7 +9,7 @@ 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.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/SupplementaryContentMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/SupplementaryContentMapperUnit.java index 57b0f78a..a89cf381 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/SupplementaryContentMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/SupplementaryContentMapperUnit.java @@ -16,7 +16,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/EanMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/EanMapperUnit.java index ee24348b..82a4a671 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/EanMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/EanMapperUnit.java @@ -21,7 +21,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/IsbnMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/IsbnMapperUnit.java index 1526764e..efb700a8 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/IsbnMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/IsbnMapperUnit.java @@ -22,7 +22,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LccnMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LccnMapperUnit.java index 5c796e25..309c4d2b 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LccnMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LccnMapperUnit.java @@ -21,7 +21,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LocalIdMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LocalIdMapperUnit.java index 9e13ef29..a8bf00bf 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LocalIdMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/LocalIdMapperUnit.java @@ -21,7 +21,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/OtherIdMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/OtherIdMapperUnit.java index a4cf47dc..a4a02b1a 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/OtherIdMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/identified/OtherIdMapperUnit.java @@ -21,7 +21,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/DistributionMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/DistributionMapperUnit.java index 4a859ef3..a75492c6 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/DistributionMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/DistributionMapperUnit.java @@ -6,7 +6,7 @@ import org.folio.linked.data.domain.dto.ProviderEventRequest; import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ManufactureMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ManufactureMapperUnit.java index a80993a2..c0f1acd8 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ManufactureMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ManufactureMapperUnit.java @@ -6,7 +6,7 @@ import org.folio.linked.data.domain.dto.ProviderEventRequest; import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProductionMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProductionMapperUnit.java index 4c48a26b..c5f3b260 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProductionMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProductionMapperUnit.java @@ -6,7 +6,7 @@ import org.folio.linked.data.domain.dto.ProviderEventRequest; import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProviderEventMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProviderEventMapperUnit.java index 275ce550..edafce87 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProviderEventMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/ProviderEventMapperUnit.java @@ -23,7 +23,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.monograph.instance.sub.InstanceSubResourceMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; @RequiredArgsConstructor public abstract class ProviderEventMapperUnit implements InstanceSubResourceMapperUnit { diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/PublicationMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/PublicationMapperUnit.java index 51e80898..c54c02be 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/PublicationMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/instance/sub/provision/PublicationMapperUnit.java @@ -6,7 +6,7 @@ import org.folio.linked.data.domain.dto.ProviderEventRequest; import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component 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 8cf473d8..3c617981 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 @@ -42,7 +42,7 @@ import org.folio.linked.data.mapper.dto.monograph.TopResourceMapperUnit; import org.folio.linked.data.mapper.dto.monograph.common.NoteMapper; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ClassificationMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ClassificationMapperUnit.java index 717dabfd..b4773ee7 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ClassificationMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ClassificationMapperUnit.java @@ -23,7 +23,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnit.java index 01f6e34d..81be3df1 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnit.java @@ -9,7 +9,7 @@ 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.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/DissertationMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/DissertationMapperUnit.java index 87b4a6d0..927e37d3 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/DissertationMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/DissertationMapperUnit.java @@ -20,7 +20,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapper; import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnit.java index f35d23ed..e6c8c8fc 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnit.java @@ -10,7 +10,7 @@ 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.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/LanguageMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/LanguageMapperUnit.java index 0df8ce3e..f7626326 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/LanguageMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/LanguageMapperUnit.java @@ -12,7 +12,7 @@ import org.folio.linked.data.mapper.dto.common.MapperUnit; import org.folio.linked.data.mapper.dto.monograph.common.CategoryMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnit.java index e92709f3..777b5944 100644 --- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnit.java +++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnit.java @@ -10,7 +10,7 @@ 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.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java b/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java index 52c80d87..68a40347 100644 --- a/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java +++ b/src/main/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapper.java @@ -2,6 +2,7 @@ import static org.mapstruct.MappingConstants.ComponentModel.SPRING; +import java.util.UUID; import org.folio.linked.data.mapper.ResourceModelMapper; import org.folio.linked.data.model.entity.Resource; import org.folio.marc4ld.service.ld2marc.Bibframe2MarcMapper; @@ -13,7 +14,7 @@ import org.mapstruct.MappingTarget; import org.springframework.beans.factory.annotation.Autowired; -@Mapper(componentModel = SPRING) +@Mapper(componentModel = SPRING, imports = UUID.class) public abstract class InstanceIngressMessageMapper { private static final String LINKED_DATA_ID = "linkedDataId"; @@ -22,6 +23,7 @@ public abstract class InstanceIngressMessageMapper { @Autowired protected ResourceModelMapper resourceModelMapper; + @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())") @Mapping(target = "eventPayload", source = "resource") public abstract InstanceIngressEvent toInstanceIngressEvent(Resource resource); diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapper.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapper.java index ddab3873..4a9be6b7 100644 --- a/src/main/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapper.java +++ b/src/main/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapper.java @@ -5,24 +5,26 @@ import static org.mapstruct.MappingConstants.ComponentModel.SPRING; import java.util.List; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.folio.ld.dictionary.ResourceTypeDictionary; import org.folio.linked.data.mapper.kafka.search.identifier.IndexIdentifierMapper; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceTypeEntity; -import org.folio.search.domain.dto.BibframeAuthorityIdentifiersInner; import org.folio.search.domain.dto.LinkedDataAuthority; +import org.folio.search.domain.dto.LinkedDataAuthorityIdentifiersInner; import org.folio.search.domain.dto.ResourceIndexEvent; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.springframework.beans.factory.annotation.Autowired; -@Mapper(componentModel = SPRING) +@Mapper(componentModel = SPRING, imports = UUID.class) public abstract class AuthoritySearchMessageMapper { @Autowired - protected IndexIdentifierMapper innerIndexIdentifierMapper; + protected IndexIdentifierMapper innerIndexIdentifierMapper; + @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())") @Mapping(target = "resourceName", constant = SEARCH_AUTHORITY_RESOURCE_NAME) @Mapping(target = "_new", expression = "java(toLinkedDataAuthority(resource))") public abstract ResourceIndexEvent toIndex(Resource resource); @@ -31,7 +33,7 @@ public abstract class AuthoritySearchMessageMapper { @Mapping(target = "identifiers", source = "resource") protected abstract LinkedDataAuthority toLinkedDataAuthority(Resource resource); - protected List extractIdentifiers(Resource resource) { + protected List extractIdentifiers(Resource resource) { return innerIndexIdentifierMapper.extractIdentifiers(resource); } diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapper.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapper.java index a23fdbbb..73b942a8 100644 --- a/src/main/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapper.java +++ b/src/main/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapper.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -50,17 +51,17 @@ 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.search.domain.dto.BibframeClassificationsInner; -import org.folio.search.domain.dto.BibframeContributorsInner; -import org.folio.search.domain.dto.BibframeInstancesInner; -import org.folio.search.domain.dto.BibframeInstancesInnerEditionStatementsInner; -import org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner; -import org.folio.search.domain.dto.BibframeInstancesInnerPublicationsInner; -import org.folio.search.domain.dto.BibframeLanguagesInner; -import org.folio.search.domain.dto.BibframeSubjectsInner; -import org.folio.search.domain.dto.BibframeTitlesInner; import org.folio.search.domain.dto.LinkedDataWork; +import org.folio.search.domain.dto.LinkedDataWorkClassificationsInner; +import org.folio.search.domain.dto.LinkedDataWorkContributorsInner; import org.folio.search.domain.dto.LinkedDataWorkIndexTitleType; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInner; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInnerEditionStatementsInner; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInnerPublicationsInner; +import org.folio.search.domain.dto.LinkedDataWorkLanguagesInner; +import org.folio.search.domain.dto.LinkedDataWorkSubjectsInner; +import org.folio.search.domain.dto.LinkedDataWorkTitlesInner; import org.folio.search.domain.dto.ResourceIndexEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -69,14 +70,15 @@ import org.springframework.beans.factory.annotation.Autowired; @Log4j2 -@Mapper(componentModel = SPRING) +@Mapper(componentModel = SPRING, imports = UUID.class) public abstract class BibliographicSearchMessageMapper { @Autowired - private IndexIdentifierMapper innerIndexIdentifierMapper; + private IndexIdentifierMapper innerIndexIdentifierMapper; @Autowired private SingleResourceMapper singleResourceMapper; + @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())") @Mapping(target = "resourceName", constant = SEARCH_RESOURCE_NAME) @Mapping(target = "_new", expression = "java(toLinkedDataWork(resource))") public abstract ResourceIndexEvent toIndex(Resource resource); @@ -89,12 +91,12 @@ public abstract class BibliographicSearchMessageMapper { @Mapping(target = "instances", source = "resource") protected abstract LinkedDataWork toLinkedDataWork(Resource resource); - protected List extractTitles(Resource resource) { + protected List extractTitles(Resource resource) { return resource.getOutgoingEdges().stream() .filter(re -> TITLE.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) .flatMap(t -> { - var titles = new ArrayList(); + var titles = new ArrayList(); addTitle(t, MAIN_TITLE, titles); addTitle(t, SUBTITLE, titles); return titles.stream(); @@ -103,13 +105,13 @@ protected List extractTitles(Resource resource) { .toList(); } - protected void addTitle(Resource t, PropertyDictionary field, List titles) { + protected void addTitle(Resource t, PropertyDictionary field, List titles) { var titleText = getValue(t.getDoc(), field.getValue()); if (nonNull(titleText)) { var titleType = getTitleType(t); ofNullable(titleType) .map(type -> getIndexTitleType(type, field)) - .map(indexTitleType -> new BibframeTitlesInner().value(titleText).type(indexTitleType)) + .map(indexTitleType -> new LinkedDataWorkTitlesInner().value(titleText).type(indexTitleType)) .ifPresent(titles::add); } } @@ -138,14 +140,14 @@ protected LinkedDataWorkIndexTitleType getIndexTitleType(ResourceTypeDictionary }; } - protected List extractContributors(Resource resource) { + protected List extractContributors(Resource resource) { return resource.getOutgoingEdges().stream() .filter(re -> CREATOR.getUri().equals(re.getPredicate().getUri()) || CONTRIBUTOR.getUri().equals(re.getPredicate().getUri())) - .map(re -> new BibframeContributorsInner() + .map(re -> new LinkedDataWorkContributorsInner() .name(getValue(re.getTarget().getDoc(), NAME.getValue())) - .type(toType(re.getTarget(), BibframeContributorsInner.TypeEnum::fromValue, - BibframeContributorsInner.TypeEnum.class, re.getPredicate(), WorkResponse.class)) + .type(toType(re.getTarget(), LinkedDataWorkContributorsInner.TypeEnum::fromValue, + LinkedDataWorkContributorsInner.TypeEnum.class, re.getPredicate(), WorkResponse.class)) .isCreator(CREATOR.getUri().equals(re.getPredicate().getUri())) ) .filter(ic -> nonNull(ic.getName())) @@ -153,14 +155,14 @@ protected List extractContributors(Resource resource) .toList(); } - protected List extractLanguages(Resource work) { + protected List extractLanguages(Resource work) { return work.getOutgoingEdges() .stream() .filter(re -> LANGUAGE.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) .map(Resource::getDoc) .flatMap(d -> getPropertyValues(d, CODE.getValue())) - .map(pv -> new BibframeLanguagesInner().value(pv)) + .map(pv -> new LinkedDataWorkLanguagesInner().value(pv)) .toList(); } @@ -172,11 +174,11 @@ protected Stream getPropertyValues(JsonNode doc, String... properties) { .flatMap(p -> StreamSupport.stream(doc.get(p).spliterator(), true).map(JsonNode::asText))); } - protected List extractClassifications(Resource resource) { + protected List extractClassifications(Resource resource) { return resource.getOutgoingEdges().stream() .filter(re -> CLASSIFICATION.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) - .map(tr -> new BibframeClassificationsInner() + .map(tr -> new LinkedDataWorkClassificationsInner() .number(getValue(tr.getDoc(), CODE.getValue())) .source(getValue(tr.getDoc(), SOURCE.getValue()))) .filter(bci -> nonNull(bci.getNumber())) @@ -184,18 +186,18 @@ protected List extractClassifications(Resource res .toList(); } - protected List extractSubjects(Resource resource) { + protected List extractSubjects(Resource resource) { return resource.getOutgoingEdges().stream() .filter(re -> SUBJECT.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) - .map(tr -> new BibframeSubjectsInner() + .map(tr -> new LinkedDataWorkSubjectsInner() .value(tr.getLabel())) .filter(bci -> nonNull(bci.getValue())) .distinct() .toList(); } - protected List extractInstances(Resource resource) { + protected List extractInstances(Resource resource) { var workStream = resource.isOfType(INSTANCE) ? resource.getOutgoingEdges().stream() .filter(re -> INSTANTIATES.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) : Stream.of(resource); @@ -203,14 +205,14 @@ protected List extractInstances(Resource resource) { .flatMap(work -> work.getIncomingEdges().stream() .filter(re -> INSTANTIATES.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getSource)) - .map(ir -> new BibframeInstancesInner() + .map(ir -> new LinkedDataWorkInstancesInner() .id(String.valueOf(ir.getId())) .titles(extractTitles(ir)) .identifiers(innerIndexIdentifierMapper.extractIdentifiers(ir)) .contributors(extractContributors(ir)) .publications(extractPublications(ir)) .editionStatements(getPropertyValues(ir.getDoc(), EDITION_STATEMENT.getValue()) - .map(es -> new BibframeInstancesInnerEditionStatementsInner().value(es)).toList())) + .map(es -> new LinkedDataWorkInstancesInnerEditionStatementsInner().value(es)).toList())) .filter(bii -> isNotEmpty(bii.getTitles()) || isNotEmpty(bii.getIdentifiers()) || isNotEmpty(bii.getContributors()) || isNotEmpty(bii.getPublications()) || isNotEmpty(bii.getEditionStatements())) @@ -218,11 +220,11 @@ protected List extractInstances(Resource resource) { .toList(); } - protected List extractPublications(Resource resource) { + protected List extractPublications(Resource resource) { return resource.getOutgoingEdges().stream() .filter(re -> PE_PUBLICATION.getUri().equals(re.getPredicate().getUri())) .map(ResourceEdge::getTarget) - .map(ir -> new BibframeInstancesInnerPublicationsInner() + .map(ir -> new LinkedDataWorkInstancesInnerPublicationsInner() .name(getValue(ir.getDoc(), NAME.getValue())) .date(cleanDate(getValue(ir.getDoc(), DATE.getValue(), PROVIDER_DATE.getValue())))) .filter(ip -> nonNull(ip.getName()) || nonNull(ip.getDate())) diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeAuthorityIdentifiersInnerMapperAbstract.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeAuthorityIdentifiersInnerMapperAbstract.java deleted file mode 100644 index 450738f9..00000000 --- a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeAuthorityIdentifiersInnerMapperAbstract.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.folio.linked.data.mapper.kafka.search.identifier; - -import java.util.function.BiFunction; -import java.util.function.Function; -import org.folio.search.domain.dto.BibframeAuthorityIdentifiersInner; -import org.springframework.stereotype.Component; - -@Component -public class BibframeAuthorityIdentifiersInnerMapperAbstract extends - AbstractIndexIdentifierMapper { - - @Override - protected Function getTypeSupplier() { - return BibframeAuthorityIdentifiersInner.TypeEnum::fromValue; - } - - @Override - protected Function getIndexCreateByValueFunction() { - return s -> new BibframeAuthorityIdentifiersInner().value(s); - } - - @Override - protected BiFunction getIndexUpdateTypeFunction() { - return BibframeAuthorityIdentifiersInner::type; - } -} diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeInstancesInnerIdentifiersInnerMapperAbstract.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeInstancesInnerIdentifiersInnerMapperAbstract.java deleted file mode 100644 index 657e6ad7..00000000 --- a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/BibframeInstancesInnerIdentifiersInnerMapperAbstract.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.folio.linked.data.mapper.kafka.search.identifier; - -import java.util.function.BiFunction; -import java.util.function.Function; -import org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner; -import org.springframework.stereotype.Component; - -@Component -public class BibframeInstancesInnerIdentifiersInnerMapperAbstract extends - AbstractIndexIdentifierMapper { - - @Override - protected Function getTypeSupplier() { - return BibframeInstancesInnerIdentifiersInner.TypeEnum::fromValue; - } - - @Override - protected Function getIndexCreateByValueFunction() { - return s -> new BibframeInstancesInnerIdentifiersInner().value(s); - } - - @Override - protected BiFunction getIndexUpdateTypeFunction() { - return BibframeInstancesInnerIdentifiersInner::type; - } -} diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataAuthorityIdentifiersInnerMapperAbstract.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataAuthorityIdentifiersInnerMapperAbstract.java new file mode 100644 index 00000000..13a23299 --- /dev/null +++ b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataAuthorityIdentifiersInnerMapperAbstract.java @@ -0,0 +1,27 @@ +package org.folio.linked.data.mapper.kafka.search.identifier; + +import java.util.function.BiFunction; +import java.util.function.Function; +import org.folio.search.domain.dto.LinkedDataAuthorityIdentifiersInner; +import org.springframework.stereotype.Component; + +@Component +public class LinkedDataAuthorityIdentifiersInnerMapperAbstract extends + AbstractIndexIdentifierMapper { + + @Override + protected Function getTypeSupplier() { + return LinkedDataAuthorityIdentifiersInner.TypeEnum::fromValue; + } + + @Override + protected Function getIndexCreateByValueFunction() { + return s -> new LinkedDataAuthorityIdentifiersInner().value(s); + } + + @Override + protected BiFunction getIndexUpdateTypeFunction() { + return LinkedDataAuthorityIdentifiersInner::type; + } +} diff --git a/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract.java b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract.java new file mode 100644 index 00000000..8e3b224b --- /dev/null +++ b/src/main/java/org/folio/linked/data/mapper/kafka/search/identifier/LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract.java @@ -0,0 +1,30 @@ +package org.folio.linked.data.mapper.kafka.search.identifier; + +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum; + +import java.util.function.BiFunction; +import java.util.function.Function; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner; +import org.springframework.stereotype.Component; + +@Component +public class LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract extends + AbstractIndexIdentifierMapper { + + @Override + protected Function getTypeSupplier() { + return TypeEnum::fromValue; + } + + @Override + protected Function getIndexCreateByValueFunction() { + return s -> new LinkedDataWorkInstancesInnerIdentifiersInner().value(s); + } + + @Override + protected BiFunction getIndexUpdateTypeFunction() { + return LinkedDataWorkInstancesInnerIdentifiersInner::type; + } +} diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceCreatedEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceCreatedEvent.java index 2c5d2d95..ad04ecfe 100644 --- a/src/main/java/org/folio/linked/data/model/entity/event/ResourceCreatedEvent.java +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceCreatedEvent.java @@ -2,5 +2,5 @@ import org.folio.linked.data.model.entity.Resource; -public record ResourceCreatedEvent(Resource resource) { +public record ResourceCreatedEvent(Resource resource) implements ResourceEvent { } diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceDeletedEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceDeletedEvent.java index 2b23968e..a0be862f 100644 --- a/src/main/java/org/folio/linked/data/model/entity/event/ResourceDeletedEvent.java +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceDeletedEvent.java @@ -2,5 +2,5 @@ import org.folio.linked.data.model.entity.Resource; -public record ResourceDeletedEvent(Resource resource) { +public record ResourceDeletedEvent(Resource resource) implements ResourceEvent { } diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceEvent.java new file mode 100644 index 00000000..fec4d7db --- /dev/null +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceEvent.java @@ -0,0 +1,4 @@ +package org.folio.linked.data.model.entity.event; + +public interface ResourceEvent { +} diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceIndexedEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceIndexedEvent.java index fd2cab3c..32ae9450 100644 --- a/src/main/java/org/folio/linked/data/model/entity/event/ResourceIndexedEvent.java +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceIndexedEvent.java @@ -1,4 +1,4 @@ package org.folio.linked.data.model.entity.event; -public record ResourceIndexedEvent(Long resourceId) { +public record ResourceIndexedEvent(Long resourceId) implements ResourceEvent { } diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceReplacedEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceReplacedEvent.java index 51993ffa..a8c5bad8 100644 --- a/src/main/java/org/folio/linked/data/model/entity/event/ResourceReplacedEvent.java +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceReplacedEvent.java @@ -2,5 +2,5 @@ import org.folio.linked.data.model.entity.Resource; -public record ResourceReplacedEvent(Resource previous, Resource current) { +public record ResourceReplacedEvent(Resource previous, Resource current) implements ResourceEvent { } diff --git a/src/main/java/org/folio/linked/data/model/entity/event/ResourceUpdatedEvent.java b/src/main/java/org/folio/linked/data/model/entity/event/ResourceUpdatedEvent.java index 56764c80..6f0e4014 100644 --- a/src/main/java/org/folio/linked/data/model/entity/event/ResourceUpdatedEvent.java +++ b/src/main/java/org/folio/linked/data/model/entity/event/ResourceUpdatedEvent.java @@ -2,5 +2,5 @@ import org.folio.linked.data.model.entity.Resource; -public record ResourceUpdatedEvent(Resource resource) { +public record ResourceUpdatedEvent(Resource resource) implements ResourceEvent { } diff --git a/src/main/java/org/folio/linked/data/repo/InstanceMetadataRepository.java b/src/main/java/org/folio/linked/data/repo/InstanceMetadataRepository.java index 94fa3c69..df4f7a8f 100644 --- a/src/main/java/org/folio/linked/data/repo/InstanceMetadataRepository.java +++ b/src/main/java/org/folio/linked/data/repo/InstanceMetadataRepository.java @@ -6,7 +6,9 @@ public interface InstanceMetadataRepository extends JpaRepository { - Optional findByInventoryId(String inventoryId); + Optional findIdByInventoryId(String inventoryId); + + Optional findByInventoryId(String inventoryId); interface IdOnly { Long getId(); diff --git a/src/main/java/org/folio/linked/data/repo/ResourceRepository.java b/src/main/java/org/folio/linked/data/repo/ResourceRepository.java index 13f9c8ba..38abd83d 100644 --- a/src/main/java/org/folio/linked/data/repo/ResourceRepository.java +++ b/src/main/java/org/folio/linked/data/repo/ResourceRepository.java @@ -1,5 +1,6 @@ package org.folio.linked.data.repo; +import java.util.Optional; import java.util.Set; import org.folio.linked.data.model.ResourceShortInfo; import org.folio.linked.data.model.entity.Resource; @@ -31,4 +32,6 @@ public interface ResourceRepository extends JpaRepository { @Modifying @Query("update Resource r set r.indexDate = current_timestamp() where r.id in (:ids)") void updateIndexDateBatch(@Param("ids") Set ids); + + Optional findByInstanceMetadataInventoryId(String inventoryId); } diff --git a/src/main/java/org/folio/linked/data/service/impl/DictionaryServiceImpl.java b/src/main/java/org/folio/linked/data/service/DictionaryServiceImpl.java similarity index 93% rename from src/main/java/org/folio/linked/data/service/impl/DictionaryServiceImpl.java rename to src/main/java/org/folio/linked/data/service/DictionaryServiceImpl.java index 502c4121..331bb370 100644 --- a/src/main/java/org/folio/linked/data/service/impl/DictionaryServiceImpl.java +++ b/src/main/java/org/folio/linked/data/service/DictionaryServiceImpl.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service; import java.util.Arrays; import lombok.RequiredArgsConstructor; @@ -9,7 +9,6 @@ import org.folio.linked.data.mapper.dictionary.ResourceTypeMapper; import org.folio.linked.data.repo.PredicateRepository; import org.folio.linked.data.repo.ResourceTypeRepository; -import org.folio.linked.data.service.DictionaryService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/org/folio/linked/data/service/impl/ProfileServiceImpl.java b/src/main/java/org/folio/linked/data/service/ProfileServiceImpl.java similarity index 87% rename from src/main/java/org/folio/linked/data/service/impl/ProfileServiceImpl.java rename to src/main/java/org/folio/linked/data/service/ProfileServiceImpl.java index 28227207..9e19215b 100644 --- a/src/main/java/org/folio/linked/data/service/impl/ProfileServiceImpl.java +++ b/src/main/java/org/folio/linked/data/service/ProfileServiceImpl.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service; import static org.folio.linked.data.util.Constants.PROFILE_NOT_FOUND; @@ -6,7 +6,6 @@ import org.folio.linked.data.exception.NotFoundException; import org.folio.linked.data.model.entity.Profile; import org.folio.linked.data.repo.ProfileRepository; -import org.folio.linked.data.service.ProfileService; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/linked/data/service/impl/ResourceServiceImpl.java b/src/main/java/org/folio/linked/data/service/impl/ResourceServiceImpl.java deleted file mode 100644 index b7e3bfce..00000000 --- a/src/main/java/org/folio/linked/data/service/impl/ResourceServiceImpl.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.folio.linked.data.service.impl; - -import static java.util.Objects.isNull; -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.ObjectUtils.notEqual; -import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE; -import static org.folio.linked.data.util.BibframeUtils.extractWorkFromInstance; -import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; -import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_ID; -import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_INVENTORY_ID; - -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.folio.linked.data.domain.dto.ResourceGraphDto; -import org.folio.linked.data.domain.dto.ResourceIdDto; -import org.folio.linked.data.domain.dto.ResourceMarcViewDto; -import org.folio.linked.data.domain.dto.ResourceRequestDto; -import org.folio.linked.data.domain.dto.ResourceResponseDto; -import org.folio.linked.data.domain.dto.ResourceShortInfoPage; -import org.folio.linked.data.exception.NotFoundException; -import org.folio.linked.data.exception.ValidationException; -import org.folio.linked.data.mapper.ResourceModelMapper; -import org.folio.linked.data.mapper.dto.ResourceDtoMapper; -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.model.entity.event.ResourceCreatedEvent; -import org.folio.linked.data.model.entity.event.ResourceDeletedEvent; -import org.folio.linked.data.model.entity.event.ResourceReplacedEvent; -import org.folio.linked.data.model.entity.event.ResourceUpdatedEvent; -import org.folio.linked.data.repo.InstanceMetadataRepository; -import org.folio.linked.data.repo.ResourceEdgeRepository; -import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.ResourceService; -import org.folio.linked.data.service.resource.meta.MetadataService; -import org.folio.linked.data.util.JsonUtils; -import org.folio.marc4ld.service.ld2marc.Bibframe2MarcMapper; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class ResourceServiceImpl implements ResourceService { - - private static final int DEFAULT_PAGE_NUMBER = 0; - private static final int DEFAULT_PAGE_SIZE = 100; - private static final Sort DEFAULT_SORT = Sort.by(Sort.Direction.ASC, "label"); - private final InstanceMetadataRepository instanceMetadataRepo; - private final ResourceRepository resourceRepo; - private final ResourceEdgeRepository edgeRepo; - private final ResourceDtoMapper resourceDtoMapper; - private final ResourceModelMapper resourceModelMapper; - private final ApplicationEventPublisher applicationEventPublisher; - private final Bibframe2MarcMapper bibframe2MarcMapper; - private final MetadataService metadataService; - - @Override - public ResourceResponseDto createResource(ResourceRequestDto resourceDto) { - var mapped = resourceDtoMapper.toEntity(resourceDto); - log.info("createResource\n[{}]\nfrom DTO [{}]", mapped, resourceDto); - metadataService.ensure(mapped); - var persisted = saveMergingGraph(mapped); - applicationEventPublisher.publishEvent(new ResourceCreatedEvent(persisted)); - return resourceDtoMapper.toDto(persisted); - } - - @Override - public Long createResource(org.folio.ld.dictionary.model.Resource modelResource) { - var mapped = resourceModelMapper.toEntity(modelResource); - var persisted = saveMergingGraph(mapped); - refreshWork(persisted); - log.info("createResource [{}]\nfrom modelResource [{}]", persisted, modelResource); - applicationEventPublisher.publishEvent(new ResourceCreatedEvent(persisted)); - return persisted.getId(); - } - - @Override - @Transactional(readOnly = true) - public ResourceResponseDto getResourceById(Long id) { - var resource = getResource(id); - return resourceDtoMapper.toDto(resource); - } - - @Override - public ResourceIdDto getResourceIdByInventoryId(String inventoryId) { - return instanceMetadataRepo.findByInventoryId(inventoryId) - .map(idOnly -> new ResourceIdDto().id(String.valueOf(idOnly.getId()))) - .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_INVENTORY_ID + inventoryId + IS_NOT_FOUND)); - } - - @Override - public ResourceResponseDto updateResource(Long id, ResourceRequestDto resourceDto) { - log.info("updateResource [{}] from DTO [{}]", id, resourceDto); - var existed = getResource(id); - var oldResource = new Resource(existed); - breakEdgesAndDelete(existed); - var newResource = saveNewResource(resourceDto, existed); - if (Objects.equals(oldResource.getId(), newResource.getId())) { - applicationEventPublisher.publishEvent(new ResourceUpdatedEvent(newResource)); - } else { - applicationEventPublisher.publishEvent(new ResourceReplacedEvent(oldResource, newResource)); - } - return resourceDtoMapper.toDto(newResource); - } - - @Override - public Resource saveMergingGraph(Resource resource) { - return saveMergingGraphSkippingAlreadySaved(resource, null); - } - - private Resource saveMergingGraphSkippingAlreadySaved(Resource resource, Resource saved) { - if (resource.isNew()) { - saveOrUpdate(resource); - saveEdges(resource, resource.getOutgoingEdges(), ResourceEdge::getTarget, saved); - saveEdges(resource, resource.getIncomingEdges(), ResourceEdge::getSource, saved); - } - return resource; - } - - private void saveOrUpdate(Resource resource) { - resourceRepo.findById(resource.getId()) - .ifPresentOrElse(existing -> updateResourceDoc(existing, resource), - () -> resourceRepo.save(resource) - ); - } - - private void updateResourceDoc(Resource existing, Resource incoming) { - resourceRepo.save(existing.setDoc(JsonUtils.merge(existing.getDoc(), incoming.getDoc()))); - } - - private void saveEdges(Resource resource, Set edges, Function resourceSelector, - Resource saved) { - edges.stream() - .filter(edge -> notEqual(resourceSelector.apply(edge), saved)) - .forEach(edge -> saveEdge(resourceSelector.apply(edge), resource, edge)); - } - - private void saveEdge(Resource edgeResource, Resource resource, ResourceEdge edge) { - if (edge.isNew()) { - saveMergingGraphSkippingAlreadySaved(edgeResource, resource); - edge.computeId(); - if (doesNotExists(edge)) { - edgeRepo.save(edge); - } - } - } - - private boolean doesNotExists(ResourceEdge edge) { - return ofNullable(edge) - .map(ResourceEdge::getId) - .map(id -> !edgeRepo.existsById(id)) - .orElse(false); - } - - - @Override - public void deleteResource(Long id) { - log.info("deleteResource [{}]", id); - resourceRepo.findById(id).ifPresent(resource -> { - breakEdgesAndDelete(resource); - applicationEventPublisher.publishEvent(new ResourceDeletedEvent(resource)); - }); - } - - @Override - @Transactional(readOnly = true) - public ResourceMarcViewDto getResourceMarcViewById(Long id) { - var resource = getResource(id); - validateMarkViewSupportedType(resource); - var resourceModel = resourceModelMapper.toModel(resource); - var marc = bibframe2MarcMapper.toMarcJson(resourceModel); - return resourceDtoMapper.toMarcViewDto(resource, marc); - } - - private void validateMarkViewSupportedType(Resource resource) { - if (resource.isOfType(INSTANCE)) { - return; - } - throw new ValidationException( - "Resource is not supported for MARC view", - "type", resource.getTypes().stream() - .map(ResourceTypeEntity::getUri) - .collect(Collectors.joining(", ", "[", "]")) - ); - } - - private void breakCircularEdges(Resource resource) { - breakOutgoingCircularEdges(resource); - breakIncomingCircularEdges(resource); - } - - private void breakOutgoingCircularEdges(Resource resource) { - resource.getOutgoingEdges().forEach(edge -> { - var filtered = edge.getTarget().getIncomingEdges().stream() - .filter(e -> resource.equals(e.getSource())) - .collect(Collectors.toSet()); - edge.getTarget().getIncomingEdges().removeAll(filtered); - }); - } - - private void breakIncomingCircularEdges(Resource resource) { - resource.getIncomingEdges().forEach(edge -> { - var filtered = edge.getSource().getOutgoingEdges().stream() - .filter(e -> resource.equals(e.getTarget())) - .collect(Collectors.toSet()); - edge.getSource().getOutgoingEdges().removeAll(filtered); - }); - } - - @Override - public ResourceShortInfoPage getResourceShortInfoPage(String type, Integer pageNumber, Integer pageSize) { - if (isNull(pageNumber)) { - pageNumber = DEFAULT_PAGE_NUMBER; - } - if (isNull(pageSize)) { - pageSize = DEFAULT_PAGE_SIZE; - } - var pageRequest = PageRequest.of(pageNumber, pageSize, DEFAULT_SORT); - var page = isNull(type) ? resourceRepo.findAllShort(pageRequest) - : resourceRepo.findAllShortByType(Set.of(type), pageRequest); - var pageOfDto = page.map(resourceDtoMapper::map); - return resourceDtoMapper.map(pageOfDto); - } - - @Async - @Override - public void updateIndexDateBatch(Set ids) { - resourceRepo.updateIndexDateBatch(ids); - } - - @Override - public ResourceGraphDto getResourceGraphById(Long id) { - var resource = getResource(id); - return resourceDtoMapper.toResourceGraphDto(resource); - } - - private Resource getResource(Long id) { - return resourceRepo.findById(id) - .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND)); - } - - private Resource saveNewResource(ResourceRequestDto resourceDto, Resource old) { - var mapped = resourceDtoMapper.toEntity(resourceDto); - metadataService.ensure(mapped, old.getInstanceMetadata()); - return saveMergingGraph(mapped); - } - - private void breakEdgesAndDelete(Resource resource) { - breakCircularEdges(resource); - resourceRepo.delete(resource); - } - - private void refreshWork(Resource resource) { - if (resource.isOfType(INSTANCE)) { - extractWorkFromInstance(resource) - .ifPresent(work -> { - edgeRepo.findByIdTargetHash(work.getId()) - .forEach(work::addIncomingEdge); - addOutgoingEdges(work); - }); - } - } - - private void addOutgoingEdges(Resource resource) { - edgeRepo.findByIdSourceHash(resource.getId()) - .forEach(resource::addOutgoingEdge); - } -} diff --git a/src/main/java/org/folio/linked/data/service/BatchIndexService.java b/src/main/java/org/folio/linked/data/service/index/BatchIndexService.java similarity index 85% rename from src/main/java/org/folio/linked/data/service/BatchIndexService.java rename to src/main/java/org/folio/linked/data/service/index/BatchIndexService.java index 4a871372..602ec533 100644 --- a/src/main/java/org/folio/linked/data/service/BatchIndexService.java +++ b/src/main/java/org/folio/linked/data/service/index/BatchIndexService.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.index; import java.util.Set; import java.util.stream.Stream; diff --git a/src/main/java/org/folio/linked/data/service/impl/BatchIndexServiceImpl.java b/src/main/java/org/folio/linked/data/service/index/BatchIndexServiceImpl.java similarity index 94% rename from src/main/java/org/folio/linked/data/service/impl/BatchIndexServiceImpl.java rename to src/main/java/org/folio/linked/data/service/index/BatchIndexServiceImpl.java index 6688c8af..30c46f95 100644 --- a/src/main/java/org/folio/linked/data/service/impl/BatchIndexServiceImpl.java +++ b/src/main/java/org/folio/linked/data/service/index/BatchIndexServiceImpl.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service.index; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; @@ -11,7 +11,6 @@ import lombok.extern.log4j.Log4j2; import org.folio.linked.data.integration.kafka.sender.search.WorkCreateMessageSender; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.BatchIndexService; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/org/folio/linked/data/service/ReindexService.java b/src/main/java/org/folio/linked/data/service/index/ReindexService.java similarity index 62% rename from src/main/java/org/folio/linked/data/service/ReindexService.java rename to src/main/java/org/folio/linked/data/service/index/ReindexService.java index 48d70f3a..74c9938b 100644 --- a/src/main/java/org/folio/linked/data/service/ReindexService.java +++ b/src/main/java/org/folio/linked/data/service/index/ReindexService.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.index; public interface ReindexService { diff --git a/src/main/java/org/folio/linked/data/service/impl/ReindexServiceImpl.java b/src/main/java/org/folio/linked/data/service/index/ReindexServiceImpl.java similarity index 90% rename from src/main/java/org/folio/linked/data/service/impl/ReindexServiceImpl.java rename to src/main/java/org/folio/linked/data/service/index/ReindexServiceImpl.java index 85bda184..85b3af0f 100644 --- a/src/main/java/org/folio/linked/data/service/impl/ReindexServiceImpl.java +++ b/src/main/java/org/folio/linked/data/service/index/ReindexServiceImpl.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service.index; import static java.lang.Boolean.TRUE; import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK; @@ -8,9 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.BatchIndexService; -import org.folio.linked.data.service.ReindexService; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceService; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.PageRequest; diff --git a/src/main/java/org/folio/linked/data/service/resource/ResourceGraphService.java b/src/main/java/org/folio/linked/data/service/resource/ResourceGraphService.java new file mode 100644 index 00000000..be1e7a9c --- /dev/null +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceGraphService.java @@ -0,0 +1,15 @@ +package org.folio.linked.data.service.resource; + +import org.folio.linked.data.domain.dto.ResourceGraphDto; +import org.folio.linked.data.model.entity.Resource; + +public interface ResourceGraphService { + + ResourceGraphDto getResourceGraph(Long id); + + Resource saveMergingGraph(Resource resource); + + void breakEdgesAndDelete(Resource resource); + + +} diff --git a/src/main/java/org/folio/linked/data/service/resource/ResourceGraphServiceImpl.java b/src/main/java/org/folio/linked/data/service/resource/ResourceGraphServiceImpl.java new file mode 100644 index 00000000..1f6a9c84 --- /dev/null +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceGraphServiceImpl.java @@ -0,0 +1,118 @@ +package org.folio.linked.data.service.resource; + +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.ObjectUtils.notEqual; +import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; +import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_ID; + +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.linked.data.domain.dto.ResourceGraphDto; +import org.folio.linked.data.exception.NotFoundException; +import org.folio.linked.data.mapper.dto.ResourceDtoMapper; +import org.folio.linked.data.model.entity.Resource; +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.util.JsonUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Log4j2 +@Service +@Transactional +@RequiredArgsConstructor +public class ResourceGraphServiceImpl implements ResourceGraphService { + + private final ResourceRepository resourceRepo; + private final ResourceEdgeRepository edgeRepo; + private final ResourceDtoMapper resourceDtoMapper; + + @Override + public ResourceGraphDto getResourceGraph(Long id) { + var resource = resourceRepo.findById(id) + .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND)); + return resourceDtoMapper.toResourceGraphDto(resource); + } + + @Override + public Resource saveMergingGraph(Resource resource) { + return saveMergingGraphSkippingAlreadySaved(resource, null); + } + + @Override + public void breakEdgesAndDelete(Resource resource) { + breakCircularEdges(resource); + resourceRepo.delete(resource); + } + + private Resource saveMergingGraphSkippingAlreadySaved(Resource resource, Resource saved) { + if (resource.isNew()) { + saveOrUpdate(resource); + saveEdges(resource, resource.getOutgoingEdges(), ResourceEdge::getTarget, saved); + saveEdges(resource, resource.getIncomingEdges(), ResourceEdge::getSource, saved); + } + return resource; + } + + private void saveOrUpdate(Resource resource) { + resourceRepo.findById(resource.getId()) + .ifPresentOrElse(existing -> updateResourceDoc(existing, resource), + () -> resourceRepo.save(resource) + ); + } + + private void updateResourceDoc(Resource existing, Resource incoming) { + resourceRepo.save(existing.setDoc(JsonUtils.merge(existing.getDoc(), incoming.getDoc()))); + } + + private void saveEdges(Resource resource, Set edges, Function resourceSelector, + Resource saved) { + edges.stream() + .filter(edge -> notEqual(resourceSelector.apply(edge), saved)) + .forEach(edge -> saveEdge(resourceSelector.apply(edge), resource, edge)); + } + + private void saveEdge(Resource edgeResource, Resource resource, ResourceEdge edge) { + if (edge.isNew()) { + saveMergingGraphSkippingAlreadySaved(edgeResource, resource); + edge.computeId(); + if (doesNotExists(edge)) { + edgeRepo.save(edge); + } + } + } + + private boolean doesNotExists(ResourceEdge edge) { + return ofNullable(edge) + .map(ResourceEdge::getId) + .map(id -> !edgeRepo.existsById(id)) + .orElse(false); + } + + private void breakCircularEdges(Resource resource) { + breakOutgoingCircularEdges(resource); + breakIncomingCircularEdges(resource); + } + + private void breakOutgoingCircularEdges(Resource resource) { + resource.getOutgoingEdges().forEach(edge -> { + var filtered = edge.getTarget().getIncomingEdges().stream() + .filter(e -> resource.equals(e.getSource())) + .collect(Collectors.toSet()); + edge.getTarget().getIncomingEdges().removeAll(filtered); + }); + } + + private void breakIncomingCircularEdges(Resource resource) { + resource.getIncomingEdges().forEach(edge -> { + var filtered = edge.getSource().getOutgoingEdges().stream() + .filter(e -> resource.equals(e.getTarget())) + .collect(Collectors.toSet()); + edge.getSource().getOutgoingEdges().removeAll(filtered); + }); + } +} diff --git a/src/main/java/org/folio/linked/data/service/resource/ResourceMarcService.java b/src/main/java/org/folio/linked/data/service/resource/ResourceMarcService.java new file mode 100644 index 00000000..34eb0855 --- /dev/null +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceMarcService.java @@ -0,0 +1,11 @@ +package org.folio.linked.data.service.resource; + +import org.folio.linked.data.domain.dto.ResourceMarcViewDto; + +public interface ResourceMarcService { + + Long saveMarcResource(org.folio.ld.dictionary.model.Resource modelResource); + + ResourceMarcViewDto getResourceMarcView(Long id); + +} diff --git a/src/main/java/org/folio/linked/data/service/resource/ResourceMarcServiceImpl.java b/src/main/java/org/folio/linked/data/service/resource/ResourceMarcServiceImpl.java new file mode 100644 index 00000000..b4a35867 --- /dev/null +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceMarcServiceImpl.java @@ -0,0 +1,145 @@ +package org.folio.linked.data.service.resource; + +import static java.util.Objects.nonNull; +import static java.util.Optional.ofNullable; +import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE; +import static org.folio.linked.data.util.BibframeUtils.extractWorkFromInstance; +import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; +import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_ID; + +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.ld.dictionary.model.InstanceMetadata; +import org.folio.linked.data.domain.dto.ResourceMarcViewDto; +import org.folio.linked.data.exception.NotFoundException; +import org.folio.linked.data.exception.ValidationException; +import org.folio.linked.data.mapper.ResourceModelMapper; +import org.folio.linked.data.mapper.dto.ResourceDtoMapper; +import org.folio.linked.data.model.entity.Resource; +import org.folio.linked.data.model.entity.ResourceTypeEntity; +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.InstanceMetadataRepository; +import org.folio.linked.data.repo.ResourceEdgeRepository; +import org.folio.linked.data.repo.ResourceRepository; +import org.folio.marc4ld.service.ld2marc.Bibframe2MarcMapper; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Log4j2 +@Service +@Transactional +@RequiredArgsConstructor +public class ResourceMarcServiceImpl implements ResourceMarcService { + + private final ResourceRepository resourceRepo; + private final ResourceEdgeRepository edgeRepo; + private final ResourceDtoMapper resourceDtoMapper; + private final ResourceModelMapper resourceModelMapper; + private final Bibframe2MarcMapper bibframe2MarcMapper; + private final ResourceGraphService resourceGraphService; + private final InstanceMetadataRepository instanceMetadataRepo; + private final ApplicationEventPublisher applicationEventPublisher; + + + public Long saveMarcResource(org.folio.ld.dictionary.model.Resource modelResource) { + var incomingInvId = ofNullable(modelResource.getInstanceMetadata()) + .map(InstanceMetadata::getInventoryId) + .orElse(null); + var existedInvId = instanceMetadataRepo.findById(modelResource.getId()) + .map(org.folio.linked.data.model.entity.InstanceMetadata::getInventoryId) + .orElse(null); + var mapped = resourceModelMapper.toEntity(modelResource); + + if (resourceRepo.existsById(modelResource.getId())) { + return updateResource(modelResource.getId(), incomingInvId, existedInvId, mapped); + } + if (nonNull(existedInvId)) { + return replaceResource(modelResource.getId(), incomingInvId, existedInvId, mapped); + } + return createResource(modelResource.getId(), incomingInvId, mapped); + } + + @Override + @Transactional(readOnly = true) + public ResourceMarcViewDto getResourceMarcView(Long id) { + var resource = resourceRepo.findById(id) + .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND)); + validateMarkViewSupportedType(resource); + var resourceModel = resourceModelMapper.toModel(resource); + var marc = bibframe2MarcMapper.toMarcJson(resourceModel); + return resourceDtoMapper.toMarcViewDto(resource, marc); + } + + private void validateMarkViewSupportedType(Resource resource) { + if (resource.isOfType(INSTANCE)) { + return; + } + throw new ValidationException( + "Resource is not supported for MARC view", + "type", resource.getTypes().stream() + .map(ResourceTypeEntity::getUri) + .collect(Collectors.joining(", ", "[", "]")) + ); + } + + private Long createResource(Long incomingId, String incomingInvId, Resource mapped) { + logMarcAction(incomingId, incomingInvId, "not found by id and invId", "created"); + return saveAndPublishEvent(mapped, ResourceCreatedEvent::new); + } + + private Long replaceResource(Long incomingId, String incomingInvId, String existedInvId, Resource mapped) { + return resourceRepo.findByInstanceMetadataInventoryId(existedInvId) + .map(Resource::new) + .map(existedByInventoryId -> { + logMarcAction(incomingId, incomingInvId, + "not found by id, but found by invId [" + existedInvId + "]", "replaced"); + return saveAndPublishEvent(mapped, + saved -> new ResourceReplacedEvent(existedByInventoryId, saved)); + }) + .orElseThrow(() -> new NotFoundException("Resource not found by existed inventory id: " + existedInvId)); + } + + private Long updateResource(Long incomingId, String incomingInvId, String existedInvId, Resource mapped) { + logMarcAction(incomingId, incomingInvId, + "found by id [" + incomingId + "] with invId [" + existedInvId + "]", "updated"); + return saveAndPublishEvent(mapped, ResourceUpdatedEvent::new); + } + + private void logMarcAction(Long incomingId, String incomingInvId, String existence, String action) { + log.info("Incoming marc resource [id {}, inventoryId {}] is {} and will be {}", + incomingId, incomingInvId, existence, action); + } + + private Long saveAndPublishEvent(Resource mapped, Function resourceEventSupplier) { + var newResource = resourceGraphService.saveMergingGraph(mapped); + refreshWork(newResource); + var event = resourceEventSupplier.apply(newResource); + if (event instanceof ResourceReplacedEvent rre) { + resourceGraphService.breakEdgesAndDelete(rre.previous()); + } + applicationEventPublisher.publishEvent(event); + return newResource.getId(); + } + + private void refreshWork(Resource resource) { + if (resource.isOfType(INSTANCE)) { + extractWorkFromInstance(resource) + .ifPresent(work -> { + edgeRepo.findByIdTargetHash(work.getId()) + .forEach(work::addIncomingEdge); + addOutgoingEdges(work); + }); + } + } + + private void addOutgoingEdges(Resource resource) { + edgeRepo.findByIdSourceHash(resource.getId()) + .forEach(resource::addOutgoingEdge); + } +} diff --git a/src/main/java/org/folio/linked/data/service/ResourceService.java b/src/main/java/org/folio/linked/data/service/resource/ResourceService.java similarity index 63% rename from src/main/java/org/folio/linked/data/service/ResourceService.java rename to src/main/java/org/folio/linked/data/service/resource/ResourceService.java index 4839d62b..eecc9564 100644 --- a/src/main/java/org/folio/linked/data/service/ResourceService.java +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceService.java @@ -1,20 +1,15 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.resource; import java.util.Set; -import org.folio.linked.data.domain.dto.ResourceGraphDto; import org.folio.linked.data.domain.dto.ResourceIdDto; -import org.folio.linked.data.domain.dto.ResourceMarcViewDto; import org.folio.linked.data.domain.dto.ResourceRequestDto; import org.folio.linked.data.domain.dto.ResourceResponseDto; import org.folio.linked.data.domain.dto.ResourceShortInfoPage; -import org.folio.linked.data.model.entity.Resource; public interface ResourceService { ResourceResponseDto createResource(ResourceRequestDto resourceRequest); - Long createResource(org.folio.ld.dictionary.model.Resource resource); - ResourceResponseDto getResourceById(Long id); ResourceIdDto getResourceIdByInventoryId(String inventoryId); @@ -23,13 +18,8 @@ public interface ResourceService { void deleteResource(Long id); - ResourceMarcViewDto getResourceMarcViewById(Long id); - ResourceShortInfoPage getResourceShortInfoPage(String type, Integer pageNumber, Integer pageSize); void updateIndexDateBatch(Set ids); - ResourceGraphDto getResourceGraphById(Long id); - - Resource saveMergingGraph(Resource resource); } diff --git a/src/main/java/org/folio/linked/data/service/resource/ResourceServiceImpl.java b/src/main/java/org/folio/linked/data/service/resource/ResourceServiceImpl.java new file mode 100644 index 00000000..f90cd0c9 --- /dev/null +++ b/src/main/java/org/folio/linked/data/service/resource/ResourceServiceImpl.java @@ -0,0 +1,130 @@ +package org.folio.linked.data.service.resource; + +import static java.util.Objects.isNull; +import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; +import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_ID; +import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_INVENTORY_ID; + +import java.util.Objects; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.folio.linked.data.domain.dto.ResourceIdDto; +import org.folio.linked.data.domain.dto.ResourceRequestDto; +import org.folio.linked.data.domain.dto.ResourceResponseDto; +import org.folio.linked.data.domain.dto.ResourceShortInfoPage; +import org.folio.linked.data.exception.NotFoundException; +import org.folio.linked.data.mapper.dto.ResourceDtoMapper; +import org.folio.linked.data.model.entity.Resource; +import org.folio.linked.data.model.entity.event.ResourceCreatedEvent; +import org.folio.linked.data.model.entity.event.ResourceDeletedEvent; +import org.folio.linked.data.model.entity.event.ResourceReplacedEvent; +import org.folio.linked.data.model.entity.event.ResourceUpdatedEvent; +import org.folio.linked.data.repo.InstanceMetadataRepository; +import org.folio.linked.data.repo.ResourceRepository; +import org.folio.linked.data.service.resource.meta.MetadataService; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class ResourceServiceImpl implements ResourceService { + + private static final int DEFAULT_PAGE_NUMBER = 0; + private static final int DEFAULT_PAGE_SIZE = 100; + private static final Sort DEFAULT_SORT = Sort.by(Sort.Direction.ASC, "label"); + private final InstanceMetadataRepository instanceMetadataRepo; + private final ResourceRepository resourceRepo; + private final ResourceDtoMapper resourceDtoMapper; + private final ApplicationEventPublisher applicationEventPublisher; + private final MetadataService metadataService; + private final ResourceGraphService resourceGraphService; + + @Override + public ResourceResponseDto createResource(ResourceRequestDto resourceDto) { + var mapped = resourceDtoMapper.toEntity(resourceDto); + log.info("createResource\n[{}]\nfrom DTO [{}]", mapped, resourceDto); + metadataService.ensure(mapped); + var persisted = resourceGraphService.saveMergingGraph(mapped); + applicationEventPublisher.publishEvent(new ResourceCreatedEvent(persisted)); + return resourceDtoMapper.toDto(persisted); + } + + @Override + @Transactional(readOnly = true) + public ResourceResponseDto getResourceById(Long id) { + var resource = getResource(id); + return resourceDtoMapper.toDto(resource); + } + + @Override + public ResourceIdDto getResourceIdByInventoryId(String inventoryId) { + return instanceMetadataRepo.findIdByInventoryId(inventoryId) + .map(idOnly -> new ResourceIdDto().id(String.valueOf(idOnly.getId()))) + .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_INVENTORY_ID + inventoryId + IS_NOT_FOUND)); + } + + @Override + public ResourceResponseDto updateResource(Long id, ResourceRequestDto resourceDto) { + log.info("updateResource [{}] from DTO [{}]", id, resourceDto); + var existed = getResource(id); + var oldResource = new Resource(existed); + resourceGraphService.breakEdgesAndDelete(existed); + var newResource = saveNewResource(resourceDto, existed); + if (Objects.equals(oldResource.getId(), newResource.getId())) { + applicationEventPublisher.publishEvent(new ResourceUpdatedEvent(newResource)); + } else { + applicationEventPublisher.publishEvent(new ResourceReplacedEvent(oldResource, newResource)); + } + return resourceDtoMapper.toDto(newResource); + } + + + @Override + public void deleteResource(Long id) { + log.info("deleteResource [{}]", id); + resourceRepo.findById(id).ifPresent(resource -> { + resourceGraphService.breakEdgesAndDelete(resource); + applicationEventPublisher.publishEvent(new ResourceDeletedEvent(resource)); + }); + } + + @Override + public ResourceShortInfoPage getResourceShortInfoPage(String type, Integer pageNumber, Integer pageSize) { + if (isNull(pageNumber)) { + pageNumber = DEFAULT_PAGE_NUMBER; + } + if (isNull(pageSize)) { + pageSize = DEFAULT_PAGE_SIZE; + } + var pageRequest = PageRequest.of(pageNumber, pageSize, DEFAULT_SORT); + var page = isNull(type) ? resourceRepo.findAllShort(pageRequest) + : resourceRepo.findAllShortByType(Set.of(type), pageRequest); + var pageOfDto = page.map(resourceDtoMapper::map); + return resourceDtoMapper.map(pageOfDto); + } + + @Async + @Override + public void updateIndexDateBatch(Set ids) { + resourceRepo.updateIndexDateBatch(ids); + } + + private Resource getResource(Long id) { + return resourceRepo.findById(id) + .orElseThrow(() -> new NotFoundException(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND)); + } + + private Resource saveNewResource(ResourceRequestDto resourceDto, Resource old) { + var mapped = resourceDtoMapper.toEntity(resourceDto); + metadataService.ensure(mapped, old.getInstanceMetadata()); + return resourceGraphService.saveMergingGraph(mapped); + } + +} diff --git a/src/main/java/org/folio/linked/data/service/HashService.java b/src/main/java/org/folio/linked/data/service/resource/hash/HashService.java similarity index 74% rename from src/main/java/org/folio/linked/data/service/HashService.java rename to src/main/java/org/folio/linked/data/service/resource/hash/HashService.java index 2b6c5641..0d21d082 100644 --- a/src/main/java/org/folio/linked/data/service/HashService.java +++ b/src/main/java/org/folio/linked/data/service/resource/hash/HashService.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.resource.hash; import lombok.NonNull; diff --git a/src/main/java/org/folio/linked/data/service/impl/HashServiceImpl.java b/src/main/java/org/folio/linked/data/service/resource/hash/HashServiceImpl.java similarity index 87% rename from src/main/java/org/folio/linked/data/service/impl/HashServiceImpl.java rename to src/main/java/org/folio/linked/data/service/resource/hash/HashServiceImpl.java index 1580f04f..97acd8b6 100644 --- a/src/main/java/org/folio/linked/data/service/impl/HashServiceImpl.java +++ b/src/main/java/org/folio/linked/data/service/resource/hash/HashServiceImpl.java @@ -1,11 +1,10 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service.resource.hash; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.folio.ld.fingerprint.service.FingerprintHashService; import org.folio.linked.data.mapper.ResourceModelMapper; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/LinkedTenantService.java b/src/main/java/org/folio/linked/data/service/tenant/LinkedTenantService.java similarity index 93% rename from src/main/java/org/folio/linked/data/service/impl/tenant/LinkedTenantService.java rename to src/main/java/org/folio/linked/data/service/tenant/LinkedTenantService.java index 2470eebe..349d8c64 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/LinkedTenantService.java +++ b/src/main/java/org/folio/linked/data/service/tenant/LinkedTenantService.java @@ -1,9 +1,10 @@ -package org.folio.linked.data.service.impl.tenant; +package org.folio.linked.data.service.tenant; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; import java.util.Collection; import lombok.extern.log4j.Log4j2; +import org.folio.linked.data.service.tenant.worker.TenantServiceWorker; import org.folio.spring.FolioExecutionContext; import org.folio.spring.liquibase.FolioSpringLiquibase; import org.folio.spring.service.TenantService; diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/TenantScopedExecutionService.java b/src/main/java/org/folio/linked/data/service/tenant/TenantScopedExecutionService.java similarity index 98% rename from src/main/java/org/folio/linked/data/service/impl/tenant/TenantScopedExecutionService.java rename to src/main/java/org/folio/linked/data/service/tenant/TenantScopedExecutionService.java index 71cda810..bf7e7065 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/TenantScopedExecutionService.java +++ b/src/main/java/org/folio/linked/data/service/tenant/TenantScopedExecutionService.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant; +package org.folio.linked.data.service.tenant; import static java.util.stream.Collectors.toMap; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorker.java b/src/main/java/org/folio/linked/data/service/tenant/worker/DictionaryWorker.java similarity index 84% rename from src/main/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorker.java rename to src/main/java/org/folio/linked/data/service/tenant/worker/DictionaryWorker.java index 8cb650b2..29068675 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorker.java +++ b/src/main/java/org/folio/linked/data/service/tenant/worker/DictionaryWorker.java @@ -1,11 +1,10 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.linked.data.service.DictionaryService; -import org.folio.linked.data.service.impl.tenant.TenantServiceWorker; import org.folio.tenant.domain.dto.TenantAttributes; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorker.java b/src/main/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorker.java similarity index 84% rename from src/main/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorker.java rename to src/main/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorker.java index 14c18e5d..3ad29a8b 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorker.java +++ b/src/main/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorker.java @@ -1,9 +1,8 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; import lombok.RequiredArgsConstructor; -import org.folio.linked.data.service.impl.tenant.TenantServiceWorker; import org.folio.spring.tools.kafka.KafkaAdminService; import org.folio.tenant.domain.dto.TenantAttributes; import org.springframework.context.annotation.Profile; diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorker.java b/src/main/java/org/folio/linked/data/service/tenant/worker/SearchWorker.java similarity index 81% rename from src/main/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorker.java rename to src/main/java/org/folio/linked/data/service/tenant/worker/SearchWorker.java index 903093a7..105cf768 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorker.java +++ b/src/main/java/org/folio/linked/data/service/tenant/worker/SearchWorker.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; import static org.folio.linked.data.util.Constants.SEARCH_RESOURCE_NAME; @@ -6,9 +6,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.linked.data.client.SearchClient; -import org.folio.linked.data.service.impl.tenant.TenantServiceWorker; import org.folio.search.domain.dto.CreateIndexRequest; -import org.folio.search.domain.dto.FolioCreateIndexResponse; +import org.folio.search.domain.dto.CreateIndexResponse; import org.folio.tenant.domain.dto.TenantAttributes; import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; @@ -26,7 +25,7 @@ public class SearchWorker implements TenantServiceWorker { public void afterTenantUpdate(String tenantId, TenantAttributes tenantAttributes) { try { var request = new CreateIndexRequest(SEARCH_RESOURCE_NAME); - ResponseEntity response = searchClient.createIndex(request); + ResponseEntity response = searchClient.createIndex(request); log.info("Index [{}] creation has been completed with a response [{}]", SEARCH_RESOURCE_NAME, response); } catch (Exception e) { if (e.getMessage().contains("Index already exists")) { diff --git a/src/main/java/org/folio/linked/data/service/impl/tenant/TenantServiceWorker.java b/src/main/java/org/folio/linked/data/service/tenant/worker/TenantServiceWorker.java similarity index 86% rename from src/main/java/org/folio/linked/data/service/impl/tenant/TenantServiceWorker.java rename to src/main/java/org/folio/linked/data/service/tenant/worker/TenantServiceWorker.java index 6ad80cae..e7dd7053 100644 --- a/src/main/java/org/folio/linked/data/service/impl/tenant/TenantServiceWorker.java +++ b/src/main/java/org/folio/linked/data/service/tenant/worker/TenantServiceWorker.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant; +package org.folio.linked.data.service.tenant.worker; import org.folio.tenant.domain.dto.TenantAttributes; diff --git a/src/main/java/org/folio/linked/data/util/KafkaUtils.java b/src/main/java/org/folio/linked/data/util/KafkaUtils.java new file mode 100644 index 00000000..dc4da9b4 --- /dev/null +++ b/src/main/java/org/folio/linked/data/util/KafkaUtils.java @@ -0,0 +1,16 @@ +package org.folio.linked.data.util; + +import static java.util.Optional.ofNullable; + +import java.util.Optional; +import lombok.experimental.UtilityClass; +import org.apache.kafka.clients.consumer.ConsumerRecord; + +@UtilityClass +public class KafkaUtils { + + public static Optional getHeaderValueByName(ConsumerRecord record, String headerName) { + return ofNullable(record.headers().lastHeader(headerName)) + .map(header -> new String(header.value())); + } +} diff --git a/src/main/resources/application-folio.yml b/src/main/resources/application-folio.yml index d162ae76..0abb2eab 100644 --- a/src/main/resources/application-folio.yml +++ b/src/main/resources/application-folio.yml @@ -22,10 +22,10 @@ folio: enabled: true kafka: listener: - data-import-instance-create: - concurrency: ${KAFKA_DATA_IMPORT_INSTANCE_CREATE_CONCURRENCY:1} - topic-pattern: ${KAFKA_DI_INSTANCE_CREATED_TOPIC_PATTERN:(${folio.environment}\.)(.*\.)DI_COMPLETED} - group-id: ${folio.environment}-linked-data-data-import-instances-created-group + source-record-domain-event: + concurrency: ${KAFKA_SOURCE_RECORD_DOMAIN_EVENT_CONCURRENCY:1} + topic-pattern: ${KAFKA_SOURCE_RECORD_DOMAIN_EVENT_TOPIC_PATTERN:(${folio.environment}\.)(.*\.)srs.source_records} + group-id: ${folio.environment}-linked-data-source-record-domain-event-group retry-interval-ms: ${KAFKA_RETRY_INTERVAL_MS:2000} retry-delivery-attempts: ${KAFKA_RETRY_DELIVERY_ATTEMPTS:6} topics: diff --git a/src/main/resources/swagger.api/events/dataImportEvent.json b/src/main/resources/swagger.api/events/dataImportEvent.json deleted file mode 100644 index 541a8678..00000000 --- a/src/main/resources/swagger.api/events/dataImportEvent.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Event from Data Import module", - "type": "object", - "properties": { - "id": { - "description": "Event ID", - "type": "string" - }, - "tenant": { - "description": "Tenant id", - "type": "string" - }, - "eventType": { - "description": "Unique Event type, human-readable String, not UUID", - "type": "string" - }, - "marcBib": { - "description": "Marc Bib record", - "type": "string" - }, - "marcAuthority": { - "description": "Marc Authority record", - "type": "string" - } - } -} diff --git a/src/main/resources/swagger.api/folio-modules.yaml b/src/main/resources/swagger.api/folio-modules.yaml index 8a1f78fd..0ec85029 100644 --- a/src/main/resources/swagger.api/folio-modules.yaml +++ b/src/main/resources/swagger.api/folio-modules.yaml @@ -8,19 +8,21 @@ paths: {} components: schemas: - searchCreateIndexRequest: - $ref: search/createIndexRequest.json - searchCreateIndexResponse: - $ref: search/folioCreateIndexResponse.json + createIndexRequest: + $ref: folio-modules/search/createIndexRequest.json + createIndexResponse: + $ref: folio-modules/search/createIndexResponse.json resourceIndexEvent: - $ref: search/resourceIndexEvent.json + $ref: folio-modules/search/resourceIndexEvent.json resourceIndexEventType: - $ref: search/resourceIndexEventType.json - bibframe: - $ref: search/linkedDataWork.json - bibframeAuthority: - $ref: search/linkedDataAuthority.json - dataImportEvent: - $ref: events/dataImportEvent.json + $ref: folio-modules/search/resourceIndexEventType.json + linkedDataWork: + $ref: folio-modules/search/linkedDataWork.json + linkedDataAuthority: + $ref: folio-modules/search/linkedDataAuthority.json instanceIngressEvent: - $ref: inventory/instanceIngressEvent.json + $ref: folio-modules/inventory/instanceIngressEvent.json + sourceRecordDomainEvent: + $ref: folio-modules/srs/sourceRecordDomainEvent.json + sourceRecordType: + $ref: folio-modules/srs/sourceRecordType.json diff --git a/src/main/resources/swagger.api/inventory/instanceIngressEvent.json b/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressEvent.json similarity index 100% rename from src/main/resources/swagger.api/inventory/instanceIngressEvent.json rename to src/main/resources/swagger.api/folio-modules/inventory/instanceIngressEvent.json diff --git a/src/main/resources/swagger.api/inventory/instanceIngressPayload.json b/src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json similarity index 100% rename from src/main/resources/swagger.api/inventory/instanceIngressPayload.json rename to src/main/resources/swagger.api/folio-modules/inventory/instanceIngressPayload.json diff --git a/src/main/resources/swagger.api/inventory/uuid.json b/src/main/resources/swagger.api/folio-modules/inventory/uuid.json similarity index 100% rename from src/main/resources/swagger.api/inventory/uuid.json rename to src/main/resources/swagger.api/folio-modules/inventory/uuid.json diff --git a/src/main/resources/swagger.api/search/createIndexRequest.json b/src/main/resources/swagger.api/folio-modules/search/createIndexRequest.json similarity index 100% rename from src/main/resources/swagger.api/search/createIndexRequest.json rename to src/main/resources/swagger.api/folio-modules/search/createIndexRequest.json diff --git a/src/main/resources/swagger.api/search/folioCreateIndexResponse.json b/src/main/resources/swagger.api/folio-modules/search/createIndexResponse.json similarity index 100% rename from src/main/resources/swagger.api/search/folioCreateIndexResponse.json rename to src/main/resources/swagger.api/folio-modules/search/createIndexResponse.json diff --git a/src/main/resources/swagger.api/search/indexDataKafkaMessage.json b/src/main/resources/swagger.api/folio-modules/search/indexDataKafkaMessage.json similarity index 100% rename from src/main/resources/swagger.api/search/indexDataKafkaMessage.json rename to src/main/resources/swagger.api/folio-modules/search/indexDataKafkaMessage.json diff --git a/src/main/resources/swagger.api/search/linkedDataAuthority.json b/src/main/resources/swagger.api/folio-modules/search/linkedDataAuthority.json similarity index 100% rename from src/main/resources/swagger.api/search/linkedDataAuthority.json rename to src/main/resources/swagger.api/folio-modules/search/linkedDataAuthority.json diff --git a/src/main/resources/swagger.api/search/linkedDataWork.json b/src/main/resources/swagger.api/folio-modules/search/linkedDataWork.json similarity index 100% rename from src/main/resources/swagger.api/search/linkedDataWork.json rename to src/main/resources/swagger.api/folio-modules/search/linkedDataWork.json diff --git a/src/main/resources/swagger.api/search/linkedDataWorkIndexTitleType.json b/src/main/resources/swagger.api/folio-modules/search/linkedDataWorkIndexTitleType.json similarity index 100% rename from src/main/resources/swagger.api/search/linkedDataWorkIndexTitleType.json rename to src/main/resources/swagger.api/folio-modules/search/linkedDataWorkIndexTitleType.json diff --git a/src/main/resources/swagger.api/search/resourceIndexEvent.json b/src/main/resources/swagger.api/folio-modules/search/resourceIndexEvent.json similarity index 100% rename from src/main/resources/swagger.api/search/resourceIndexEvent.json rename to src/main/resources/swagger.api/folio-modules/search/resourceIndexEvent.json diff --git a/src/main/resources/swagger.api/search/resourceIndexEventType.json b/src/main/resources/swagger.api/folio-modules/search/resourceIndexEventType.json similarity index 100% rename from src/main/resources/swagger.api/search/resourceIndexEventType.json rename to src/main/resources/swagger.api/folio-modules/search/resourceIndexEventType.json diff --git a/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json new file mode 100644 index 00000000..40198b04 --- /dev/null +++ b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordDomainEvent.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Source record domain event data model", + "javaType": "org.folio.rest.jaxrs.model.SourceRecordDomainEvent", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "UUID", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + "eventType": { + "type": "string", + "enum": [ + "SOURCE_RECORD_CREATED", + "SOURCE_RECORD_UPDATED" + ], + "description": "Source record domain event type" + }, + "eventMetadata": { + "description": "Event metadata", + "type": "object", + "additionalProperties": false, + "properties": { + "eventTTL": { + "description": "Time-to-live (TTL) for event in minutes", + "type": "integer" + }, + "correlationId": { + "description": "Id to track related events, can be a meaningful string or a UUID", + "type": "string" + }, + "originalEventId": { + "description": "Id of the event that started the sequence of related events", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + "publisherCallback": { + "description": "Allows a publisher to provide a callback endpoint or an error Event Type to be notified that despite the fact that there are subscribers for such an event type no one has received the event within the specified period of time", + "type": "object", + "properties": { + "endpoint": { + "description": "Callback endpoint", + "type": "string" + }, + "eventType": { + "description": "Error Event Type", + "type": "string" + } + } + }, + "createdDate": { + "description": "Timestamp when event was created", + "type": "string", + "format": "date-time" + }, + "publishedDate": { + "description": "Timestamp when event was initially published to the underlying topic", + "type": "string", + "format": "date-time" + }, + "createdBy": { + "description": "Username of the user whose action caused an event", + "type": "string" + }, + "publishedBy": { + "description": "Name and version of the module that published an event", + "type": "string" + } + }, + "required": [ + "eventTTL", + "publishedBy" + ] + }, + "eventPayload": { + "type": "string", + "description": "The source record JSON string" + } + }, + "excludedFromEqualsAndHashCode": [ + "eventMetadata" + ], + "required": [ + "id", + "eventType" + ] +} diff --git a/src/main/resources/swagger.api/folio-modules/srs/sourceRecordType.json b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordType.json new file mode 100644 index 00000000..23f20c97 --- /dev/null +++ b/src/main/resources/swagger.api/folio-modules/srs/sourceRecordType.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Record Type Enum", + "type": "string", + "additionalProperties": false, + "enum": [ + "MARC_BIB", + "MARC_AUTHORITY", + "MARC_HOLDING", + "EDIFACT" + ] +} diff --git a/src/test/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializerTest.java b/src/test/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializerTest.java deleted file mode 100644 index 3e780dbd..00000000 --- a/src/test/java/org/folio/linked/data/configuration/json/deserialization/event/DataImportEventDeserializerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.folio.linked.data.configuration.json.deserialization.event; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.linked.data.test.TestUtil.OBJECT_MAPPER; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.authorityEvent; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.dataImportEvent; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.instanceCreatedEvent; - -import java.io.IOException; -import java.util.Map; -import org.folio.search.domain.dto.DataImportEvent; -import org.folio.spring.testing.type.UnitTest; -import org.junit.jupiter.api.Test; - -@UnitTest -class DataImportEventDeserializerTest { - - @Test - void should_deserialize_instance_created_event() throws IOException { - // given - String eventId = "event_id_01"; - String tenantId = "tenant_01"; - String marc = """ - { - "leader":"01767cam a22003977i 4500", - "fields":[ - {"001":"21009122"}, - {"906":{"subfields":[{"a":"7"}]}} - }"""; - - String eventJson = instanceCreatedEvent(eventId, tenantId, marc); - - // when - DataImportEvent dataImportEvent = OBJECT_MAPPER.readValue(eventJson, DataImportEvent.class); - - // then - assertThat(eventId).isEqualTo(dataImportEvent.getId()); - assertThat(tenantId).isEqualTo(dataImportEvent.getTenant()); - assertThat(marc).isEqualTo(dataImportEvent.getMarcBib()); - assertThat(dataImportEvent.getMarcAuthority()).isNull(); - } - - @Test - void should_deserialize_authority_event() throws IOException { - // given - String eventId = "event_id_01"; - String tenantId = "tenant_01"; - String marc = """ - { - "leader":"01767cam a22003977i 4500", - "fields":[ - {"001":"21009122"}, - {"100":{"subfields":[{"a":"John Doe"}]}} - }"""; - - String eventJson = authorityEvent(eventId, tenantId, marc); - - // when - DataImportEvent dataImportEvent = OBJECT_MAPPER.readValue(eventJson, DataImportEvent.class); - - // then - assertThat(eventId).isEqualTo(dataImportEvent.getId()); - assertThat(tenantId).isEqualTo(dataImportEvent.getTenant()); - assertThat(marc).isEqualTo(dataImportEvent.getMarcAuthority()); - assertThat(dataImportEvent.getMarcBib()).isNull(); - } - - - @Test - void should_not_deserialize_unsupported_event() throws IOException { - // given - String eventId = "event_id_01"; - - String unsupportedEvent = dataImportEvent(eventId, Map.of( - "eventType", "DI_COMPLETED", - "tenant", "tenant_01", - "context", Map.of( - "MARC_BIBLIOGRAPHIC", "{}", - "CURRENT_EVENT_TYPE", "UNSUPPORTED_EVENT_TYPE" - ) - )); - - // when - DataImportEvent dataImportEvent = OBJECT_MAPPER.readValue(unsupportedEvent, DataImportEvent.class); - - // then - assertThat(dataImportEvent.getMarcBib()).isNull(); - assertThat(dataImportEvent.getMarcAuthority()).isNull(); - } -} diff --git a/src/test/java/org/folio/linked/data/controller/ResourceControllerTest.java b/src/test/java/org/folio/linked/data/controller/ResourceControllerTest.java index 5df208c9..4fb23e0c 100644 --- a/src/test/java/org/folio/linked/data/controller/ResourceControllerTest.java +++ b/src/test/java/org/folio/linked/data/controller/ResourceControllerTest.java @@ -4,7 +4,8 @@ import static org.mockito.Mockito.when; import org.folio.linked.data.domain.dto.ResourceMarcViewDto; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceMarcService; +import org.folio.linked.data.service.resource.ResourceService; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -22,6 +23,8 @@ class ResourceControllerTest { ResourceController resourceController; @Mock ResourceService resourceService; + @Mock + ResourceMarcService resourceMarcService; @Test void getResourceMarcViewById_shouldReturnOkResponse() { @@ -30,7 +33,7 @@ void getResourceMarcViewById_shouldReturnOkResponse() { var expectedDto = new ResourceMarcViewDto(); expectedDto.setId(String.valueOf(id)); - when(resourceService.getResourceMarcViewById(id)) + when(resourceMarcService.getResourceMarcView(id)) .thenReturn(expectedDto); //when diff --git a/src/test/java/org/folio/linked/data/e2e/MergeResourcesIT.java b/src/test/java/org/folio/linked/data/e2e/MergeResourcesIT.java index 80f6a1de..747db0a8 100644 --- a/src/test/java/org/folio/linked/data/e2e/MergeResourcesIT.java +++ b/src/test/java/org/folio/linked/data/e2e/MergeResourcesIT.java @@ -15,7 +15,7 @@ import org.folio.linked.data.e2e.base.IntegrationTest; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceEdge; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceGraphService; import org.folio.linked.data.test.MonographTestUtil; import org.folio.linked.data.test.ResourceTestService; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +28,7 @@ class MergeResourcesIT { @Autowired - private ResourceService resourceService; + private ResourceGraphService resourceGraphService; @Autowired private ResourceTestService resourceTestService; @Autowired @@ -45,7 +45,7 @@ public void beforeEach() { void testResourcesMerging_1() { // given var graph1 = createGraph1toto2(); - resourceService.saveMergingGraph(graph1); + resourceGraphService.saveMergingGraph(graph1); var fp1Resource = resourceTestService.getResourceById("1", 4); // Should be: 1 -> [2] assertThat(fp1Resource.getOutgoingEdges()).hasSize(1); @@ -54,7 +54,7 @@ void testResourcesMerging_1() { var graph2 = createGraph3toto1to5toto4(); // when - resourceService.saveMergingGraph(graph2); + resourceGraphService.saveMergingGraph(graph2); // then // whole graph should be: 3 -> [(1 -> [2, 5]), 4] @@ -81,7 +81,7 @@ void testResourcesMerging_1() { void testResourcesMerging_2() { // given var graph1 = createGraph3toto1to2to5toto4(); - resourceService.saveMergingGraph(graph1); + resourceGraphService.saveMergingGraph(graph1); // fp3Resource should be: 3 -> [1, 4] var fp3Resource = resourceTestService.getResourceById("3", 4); assertThat(fp3Resource.getOutgoingEdges()).hasSize(2); @@ -102,7 +102,7 @@ void testResourcesMerging_2() { var graph2 = createGraph6toto1toto4to5(); // when - resourceService.saveMergingGraph(graph2); + resourceGraphService.saveMergingGraph(graph2); // then // whole graph should be: 3 -> [(1 -> [2, 5]), 4 -> 5] @@ -147,7 +147,7 @@ void testResourcesMerging_2() { void testResourcesMerging1_and_2() { // given var graph1 = createGraph1toto2(); - resourceService.saveMergingGraph(graph1); + resourceGraphService.saveMergingGraph(graph1); var fp1Resource = resourceTestService.getResourceById("1", 4); // Should be: 1 -> [2] assertThat(fp1Resource.getOutgoingEdges()).hasSize(1); @@ -156,7 +156,7 @@ void testResourcesMerging1_and_2() { var graph2 = createGraph3toto1to5toto4(); // when - resourceService.saveMergingGraph(graph2); + resourceGraphService.saveMergingGraph(graph2); // then fp1Resource = resourceTestService.getResourceById("1", 4); @@ -182,7 +182,7 @@ void testResourcesMerging1_and_2() { // when var graph3 = createGraph6toto1toto4to5(); - resourceService.saveMergingGraph(graph3); + resourceGraphService.saveMergingGraph(graph3); // then // fp3Resource should be: 3 -> [1, 4] diff --git a/src/test/java/org/folio/linked/data/e2e/ResourceControllerFolioIT.java b/src/test/java/org/folio/linked/data/e2e/ResourceControllerFolioIT.java index 2c1be9c5..840f5e19 100644 --- a/src/test/java/org/folio/linked/data/e2e/ResourceControllerFolioIT.java +++ b/src/test/java/org/folio/linked/data/e2e/ResourceControllerFolioIT.java @@ -14,7 +14,7 @@ import org.folio.linked.data.integration.kafka.sender.search.WorkCreateMessageSender; import org.folio.linked.data.integration.kafka.sender.search.WorkDeleteMessageSender; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.impl.tenant.TenantScopedExecutionService; +import org.folio.linked.data.service.tenant.TenantScopedExecutionService; import org.folio.linked.data.test.ResourceTestService; import org.folio.linked.data.test.kafka.KafkaInventoryTopicListener; import org.folio.linked.data.test.kafka.KafkaSearchWorkIndexTopicListener; diff --git a/src/test/java/org/folio/linked/data/e2e/ResourceControllerIT.java b/src/test/java/org/folio/linked/data/e2e/ResourceControllerIT.java index 82f6c038..93210b28 100644 --- a/src/test/java/org/folio/linked/data/e2e/ResourceControllerIT.java +++ b/src/test/java/org/folio/linked/data/e2e/ResourceControllerIT.java @@ -165,7 +165,7 @@ 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.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.folio.linked.data.test.ResourceTestService; import org.folio.marc4ld.util.ResourceKind; import org.folio.search.domain.dto.ResourceIndexEventType; diff --git a/src/test/java/org/folio/linked/data/integration/KafkaMessageListenerIT.java b/src/test/java/org/folio/linked/data/integration/KafkaMessageListenerIT.java deleted file mode 100644 index ac5da7a0..00000000 --- a/src/test/java/org/folio/linked/data/integration/KafkaMessageListenerIT.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.folio.linked.data.integration; - -import static org.folio.linked.data.test.TestUtil.FOLIO_TEST_PROFILE; -import static org.folio.linked.data.test.TestUtil.awaitAndAssert; -import static org.folio.linked.data.test.TestUtil.defaultKafkaHeaders; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.instanceCreatedEvent; -import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; -import static org.folio.spring.tools.config.properties.FolioEnvironment.getFolioEnvName; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import org.apache.kafka.clients.producer.ProducerRecord; -import org.folio.linked.data.e2e.base.IntegrationTest; -import org.folio.linked.data.integration.kafka.consumer.DataImportEventHandler; -import org.folio.search.domain.dto.DataImportEvent; -import org.folio.spring.tools.kafka.KafkaAdminService; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.test.context.ActiveProfiles; - -@IntegrationTest -@ActiveProfiles({FOLIO_PROFILE, FOLIO_TEST_PROFILE}) -class KafkaMessageListenerIT { - - private static final String TENANT_ID = "tenant_01"; - private static final String DI_COMPLETED_TOPIC = "DI_COMPLETED"; - - @Autowired - private KafkaTemplate eventKafkaTemplate; - - @MockBean - private DataImportEventHandler dataImportEventConsumer; - - @BeforeAll - static void setup(@Autowired KafkaAdminService kafkaAdminService) { - kafkaAdminService.createTopics(TENANT_ID); - } - - @Test - void shouldConsumeInstanceCreatedEventFromDataImport() { - String eventId = "event_id_01"; - String tenantId = "tenant_01"; - String marc = "{}"; - - var emittedEvent = instanceCreatedEvent(eventId, tenantId, marc); - var expectedEvent = getDataImportEvent(eventId, tenantId, marc); - var producerRecord = getProducerRecord(eventId, emittedEvent); - - // when - eventKafkaTemplate.send(producerRecord); - - // then - awaitAndAssert(() -> verify(dataImportEventConsumer).handle(expectedEvent)); - } - - @Test - void shouldRetryIfErrorOccurs() { - // given - String eventId = "event_id_02"; - String tenantId = "tenant_02"; - String marc = "{}"; - - var emittedEvent = instanceCreatedEvent(eventId, tenantId, marc); - var expectedEvent = getDataImportEvent(eventId, tenantId, marc); - var producerRecord = getProducerRecord(eventId, emittedEvent); - - doThrow(new RuntimeException("An error occurred")) - .doNothing() - .when(dataImportEventConsumer).handle(expectedEvent); - - // when - eventKafkaTemplate.send(producerRecord); - - // then - awaitAndAssert(() -> verify(dataImportEventConsumer, times(2)).handle(expectedEvent)); - } - - private ProducerRecord getProducerRecord(String eventId, String emittedEvent) { - return new ProducerRecord(getTopicName(TENANT_ID, DI_COMPLETED_TOPIC), 0, - eventId, emittedEvent, defaultKafkaHeaders()); - } - - private static DataImportEvent getDataImportEvent(String eventId, String tenantId, String marc) { - return new DataImportEvent() - .id(eventId) - .tenant(tenantId) - .eventType("DI_COMPLETED") - .marcBib(marc); - } - - private String getTopicName(String tenantId, String topic) { - return String.format("%s.%s.%s", getFolioEnvName(), tenantId, topic); - } -} diff --git a/src/test/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListenerIT.java b/src/test/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListenerIT.java new file mode 100644 index 00000000..eebd84da --- /dev/null +++ b/src/test/java/org/folio/linked/data/integration/kafka/listener/KafkaMessageListenerIT.java @@ -0,0 +1,77 @@ +package org.folio.linked.data.integration.kafka.listener; + +import static org.folio.linked.data.test.TestUtil.FOLIO_TEST_PROFILE; +import static org.folio.linked.data.test.TestUtil.TENANT_ID; +import static org.folio.linked.data.test.TestUtil.awaitAndAssert; +import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.getSrsDomainEvent; +import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.getSrsDomainEventProducerRecord; +import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum.CREATED; +import static org.folio.search.domain.dto.SourceRecordType.MARC_BIB; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.folio.linked.data.e2e.base.IntegrationTest; +import org.folio.linked.data.integration.kafka.listener.handler.SourceRecordDomainEventHandler; +import org.folio.spring.tools.kafka.KafkaAdminService; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.test.context.ActiveProfiles; + +@IntegrationTest +@ActiveProfiles({FOLIO_PROFILE, FOLIO_TEST_PROFILE}) +class KafkaMessageListenerIT { + + @Autowired + private KafkaTemplate eventKafkaTemplate; + + @MockBean + private SourceRecordDomainEventHandler sourceRecordDomainEventHandler; + + @BeforeAll + static void setup(@Autowired KafkaAdminService kafkaAdminService) { + kafkaAdminService.createTopics(TENANT_ID); + } + + @Test + void shouldConsumeSrsDomainEvent() { + // given + var eventId = "event_id_01"; + var marc = "{}"; + var recordType = MARC_BIB; + var eventType = CREATED; + var eventProducerRecord = getSrsDomainEventProducerRecord(eventId, marc, eventType, recordType); + var expectedEvent = getSrsDomainEvent(eventId, marc, eventType); + + // when + eventKafkaTemplate.send(eventProducerRecord); + + // then + awaitAndAssert(() -> verify(sourceRecordDomainEventHandler).handle(expectedEvent, recordType)); + } + + @Test + void shouldRetryIfErrorOccurs() { + // given + var eventId = "event_id_02"; + var marc = "{}"; + var recordType = MARC_BIB; + var eventType = CREATED; + var eventProducerRecord = getSrsDomainEventProducerRecord(eventId, marc, eventType, recordType); + var expectedEvent = getSrsDomainEvent(eventId, marc, eventType); + doThrow(new RuntimeException("An error occurred")) + .doNothing() + .when(sourceRecordDomainEventHandler).handle(expectedEvent, recordType); + + // when + eventKafkaTemplate.send(eventProducerRecord); + + // then + awaitAndAssert(() -> verify(sourceRecordDomainEventHandler, times(2)).handle(expectedEvent, recordType)); + } + +} diff --git a/src/test/java/org/folio/linked/data/integration/DataImportEventListenerIT.java b/src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerIT.java similarity index 62% rename from src/test/java/org/folio/linked/data/integration/DataImportEventListenerIT.java rename to src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerIT.java index 54cdb068..27c66028 100644 --- a/src/test/java/org/folio/linked/data/integration/DataImportEventListenerIT.java +++ b/src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerIT.java @@ -1,5 +1,6 @@ -package org.folio.linked.data.integration; +package org.folio.linked.data.integration.kafka.listener.handler; +import static java.util.UUID.randomUUID; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.assertj.core.api.Assertions.assertThat; import static org.folio.ld.dictionary.ResourceTypeDictionary.CONCEPT; @@ -10,12 +11,13 @@ import static org.folio.linked.data.test.TestUtil.OBJECT_MAPPER; import static org.folio.linked.data.test.TestUtil.TENANT_ID; import static org.folio.linked.data.test.TestUtil.awaitAndAssert; -import static org.folio.linked.data.test.TestUtil.defaultKafkaHeaders; import static org.folio.linked.data.test.TestUtil.loadResourceAsString; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.authorityEvent; -import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.instanceCreatedEvent; +import static org.folio.linked.data.test.kafka.KafkaEventsTestDataFixture.getSrsDomainEventProducerRecord; import static org.folio.linked.data.util.Constants.FOLIO_PROFILE; import static org.folio.search.domain.dto.ResourceIndexEventType.UPDATE; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum.CREATED; +import static org.folio.search.domain.dto.SourceRecordType.MARC_AUTHORITY; +import static org.folio.search.domain.dto.SourceRecordType.MARC_BIB; import static org.folio.spring.tools.config.properties.FolioEnvironment.getFolioEnvName; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -26,20 +28,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.util.Objects; import java.util.Set; -import org.apache.kafka.clients.producer.ProducerRecord; import org.folio.linked.data.e2e.base.IntegrationTest; -import org.folio.linked.data.integration.kafka.consumer.DataImportEventHandler; +import org.folio.linked.data.integration.ResourceModificationEventListener; 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.repo.ResourceEdgeRepository; import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.ResourceService; -import org.folio.linked.data.service.impl.tenant.TenantScopedExecutionService; +import org.folio.linked.data.service.resource.ResourceMarcService; +import org.folio.linked.data.service.tenant.TenantScopedExecutionService; +import org.folio.linked.data.test.ResourceTestRepository; import org.folio.linked.data.test.kafka.KafkaSearchAuthorityAuthorityTopicListener; import org.folio.linked.data.test.kafka.KafkaSearchWorkIndexTopicListener; import org.folio.marc4ld.service.marc2ld.bib.MarcBib2ldMapper; -import org.folio.search.domain.dto.DataImportEvent; import org.folio.search.domain.dto.InstanceIngressEvent; import org.folio.spring.tools.kafka.FolioMessageProducer; import org.folio.spring.tools.kafka.KafkaAdminService; @@ -58,10 +59,7 @@ @IntegrationTest @ActiveProfiles({FOLIO_PROFILE, FOLIO_TEST_PROFILE}) -class DataImportEventListenerIT { - - private static final String DI_COMPLETED_TOPIC = "DI_COMPLETED"; - private static final String EVENT_ID_01 = "event_id_01"; +class SourceRecordDomainEventHandlerIT { @Autowired private ResourceRepository resourceRepo; @@ -69,24 +67,26 @@ class DataImportEventListenerIT { private ResourceEdgeRepository resourceEdgeRepository; @Autowired private KafkaTemplate eventKafkaTemplate; - @SpyBean - @Autowired - private DataImportEventHandler dataImportEventHandler; @Autowired private TenantScopedExecutionService tenantScopedExecutionService; @Autowired private KafkaSearchAuthorityAuthorityTopicListener kafkaSearchAuthorityAuthorityTopicListener; @Autowired private KafkaSearchWorkIndexTopicListener kafkaSearchWorkIndexTopicListener; - @SpyBean - @Autowired - private ResourceService resourceService; @MockBean private FolioMessageProducer instanceIngressMessageProducer; @Autowired private MarcBib2ldMapper marc2BibframeMapper; @Autowired private ResourceModelMapper resourceModelMapper; + @Autowired + private ResourceTestRepository resourceTestRepository; + @SpyBean + @Autowired + private ResourceMarcService resourceMarcService; + @SpyBean + @Autowired + private ResourceModificationEventListener eventListener; @BeforeAll static void beforeAll(@Autowired KafkaAdminService kafkaAdminService) { @@ -99,48 +99,50 @@ private static String getTopicName(String tenantId, String topic) { @BeforeEach public void clean() { - resourceEdgeRepository.deleteAll(); - resourceRepo.deleteAll(); - kafkaSearchAuthorityAuthorityTopicListener.getMessages().clear(); - kafkaSearchWorkIndexTopicListener.getMessages().clear(); + tenantScopedExecutionService.execute(TENANT_ID, + () -> { + resourceEdgeRepository.deleteAll(); + resourceRepo.deleteAll(); + kafkaSearchAuthorityAuthorityTopicListener.getMessages().clear(); + kafkaSearchWorkIndexTopicListener.getMessages().clear(); + } + ); } @ParameterizedTest @CsvSource({ - "samples/marc_non_monograph_leader.jsonl, 0", - "samples/marc_monograph_leader.jsonl, 1" + "samples/marc2ld/marc_non_monograph_leader.jsonl, 0", + "samples/marc2ld/marc_monograph_leader.jsonl, 1" }) void shouldNotProcessEventForNullableResource(String resource, int interactions) { // given var marc = loadResourceAsString(resource); - var emittedEvent = instanceCreatedEvent(EVENT_ID_01, TENANT_ID, marc); - var expectedEvent = newMarcBibDataImportEvent(marc); + var eventProducerRecord = getSrsDomainEventProducerRecord(randomUUID().toString(), marc, CREATED, MARC_BIB); // when - eventKafkaTemplate.send(newProducerRecord(emittedEvent)); + eventKafkaTemplate.send(eventProducerRecord); // then - awaitAndAssert(() -> verify(dataImportEventHandler).handle(expectedEvent)); - verify(resourceService, times(interactions)).createResource(any(org.folio.ld.dictionary.model.Resource.class)); + awaitAndAssert(() -> verify(resourceMarcService, times(interactions)) + .saveMarcResource(any(org.folio.ld.dictionary.model.Resource.class))); } - @Transactional @Test - void shouldProcessInstanceCreatedEventFromDataImport() { + void shouldProcessMarcBibSourceRecordDomainEvent() { // given - var marc = loadResourceAsString("samples/full_marc_sample.jsonl"); - var emittedEvent = instanceCreatedEvent(EVENT_ID_01, TENANT_ID, marc); - var expectedEvent = newMarcBibDataImportEvent(marc); + var marc = loadResourceAsString("samples/marc2ld/full_marc_sample.jsonl"); + var eventProducerRecord = getSrsDomainEventProducerRecord(randomUUID().toString(), marc, CREATED, MARC_BIB); // when - eventKafkaTemplate.send(newProducerRecord(emittedEvent)); + eventKafkaTemplate.send(eventProducerRecord); // then - awaitAndAssert(() -> verify(dataImportEventHandler).handle(expectedEvent)); + awaitAndAssert(() -> verify(resourceMarcService) + .saveMarcResource(any(org.folio.ld.dictionary.model.Resource.class))); var found = tenantScopedExecutionService.execute( TENANT_ID, - () -> resourceRepo.findAllByType(Set.of(INSTANCE.getUri()), Pageable.ofSize(1)) + () -> resourceTestRepository.findAllByTypeWithEdgesLoaded(Set.of(INSTANCE.getUri()), Pageable.ofSize(1)) .stream() .findFirst() ); @@ -166,18 +168,18 @@ void shouldProcessInstanceCreatedEventFromDataImport() { @Transactional @Test - void shouldConsumeAuthorityEventFromDataImport() { + void shouldProcessAuthoritySourceRecordDomainEvent() { // given - var marc = loadResourceAsString("samples/authority_100.jsonl"); - var emittedEvent = authorityEvent(EVENT_ID_01, TENANT_ID, marc); + var marc = loadResourceAsString("samples/marc2ld/authority_100.jsonl"); var expectedLabel = "bValue, aValue, cValue, qValue, dValue -- vValue -- xValue -- yValue -- zValue"; - var expectedEvent = newAuthorutyDataImportEvent(marc); + var eventProducerRecord = getSrsDomainEventProducerRecord(randomUUID().toString(), marc, CREATED, MARC_AUTHORITY); // when - eventKafkaTemplate.send(newProducerRecord(emittedEvent)); + eventKafkaTemplate.send(eventProducerRecord); // then - awaitAndAssert(() -> verify(dataImportEventHandler).handle(expectedEvent)); + awaitAndAssert(() -> verify(resourceMarcService) + .saveMarcResource(any(org.folio.ld.dictionary.model.Resource.class))); var found = tenantScopedExecutionService.execute( TENANT_ID, @@ -212,18 +214,19 @@ void shouldConsumeAuthorityEventFromDataImport() { } @Test - void shouldSendToIndexWorkWithTwoInstances() { - //given - var firstInstanceMarc = loadResourceAsString("samples/full_marc_sample.jsonl"); + void marcBibSourceRecordDomainEvent_shouldSendToIndexWorkWithTwoInstances() { + // given + var firstInstanceMarc = loadResourceAsString("samples/marc2ld/full_marc_sample.jsonl"); mapAndSave(firstInstanceMarc); var secondInstanceMarc = firstInstanceMarc.replace(" 2019493854", " 2019493855") .replace("code", "another code") .replace("item number", "another item number"); - var emittedEvent = instanceCreatedEvent(EVENT_ID_01, TENANT_ID, secondInstanceMarc); - var expectedMessage = loadResourceAsString("integration/kafka/search/expected_message.json"); + var expectedMessage = loadResourceAsString("samples/marc2ld/expected_message.json"); + var id = randomUUID().toString(); + var eventProducerRecord = getSrsDomainEventProducerRecord(id, secondInstanceMarc, CREATED, MARC_BIB); - //when - eventKafkaTemplate.send(newProducerRecord(emittedEvent)); + // when + eventKafkaTemplate.send(eventProducerRecord); //then awaitAndAssert(() -> { @@ -235,7 +238,7 @@ void shouldSendToIndexWorkWithTwoInstances() { try { assertThat(OBJECT_MAPPER.readValue(message, Object.class)) .usingRecursiveComparison() - .ignoringFields("ts") + .ignoringFields("id", "ts") .isEqualTo(OBJECT_MAPPER.readValue(expectedMessage, Object.class)); } catch (JsonProcessingException e) { throw new RuntimeException(e); @@ -244,6 +247,38 @@ void shouldSendToIndexWorkWithTwoInstances() { }); } + @Test + void marcBibSourceRecordDomainEvent_shouldKeepExistedEdgesAndPropertiesAndInstanceMetadata_inCaseOfUpdate() { + // given + var firstInstanceMarc = loadResourceAsString("samples/marc2ld/small_instance.jsonl"); + mapAndSave(firstInstanceMarc); + var secondInstanceMarc = loadResourceAsString("samples/marc2ld/small_instance_upd.jsonl"); + var eventId = randomUUID().toString(); + var eventProducerRecord = getSrsDomainEventProducerRecord(eventId, secondInstanceMarc, CREATED, MARC_BIB); + + // when + eventKafkaTemplate.send(eventProducerRecord); + + // then + awaitAndAssert(() -> verify(resourceMarcService) + .saveMarcResource(any(org.folio.ld.dictionary.model.Resource.class))); + verify(eventListener).afterUpdate(any()); + var allInstances = tenantScopedExecutionService.execute(TENANT_ID, + () -> resourceTestRepository.findAllByTypeWithEdgesLoaded(Set.of(INSTANCE.getUri()), Pageable.ofSize(1)) + .stream() + .toList() + ); + assertThat(allInstances).hasSize(1); + var instance = allInstances.get(0); + assertThat(instance.getDoc().toString()).isEqualTo("{\"http://bibfra.me/vocab/marc/statementOfResponsibility\":[" + + "\"Statement Of Responsibility\",\"Statement Of Responsibility UPDATED\"]}"); + assertThat(instance.getOutgoingEdges()).hasSize(5); + assertThat(instance.getInstanceMetadata()) + .hasFieldOrPropertyWithValue("inventoryId", "2165ef4b-001f-46b3-a60e-52bcdeb3d5a1") + .hasFieldOrPropertyWithValue("srsId", "43d58061-decf-4d74-9747-0e1c368e861b") + .hasFieldOrPropertyWithValue("source", MARC); + } + private void assertWorkIsIndexed(Resource instance) { var workIdOptional = instance.getOutgoingEdges() .stream() @@ -261,34 +296,16 @@ private void assertWorkIsIndexed(Resource instance) { ); } - private DataImportEvent newMarcBibDataImportEvent(String marc) { - return newDataImportEvent().marcBib(marc); - } - - private DataImportEvent newAuthorutyDataImportEvent(String marc) { - return newDataImportEvent().marcAuthority(marc); - } - - private DataImportEvent newDataImportEvent() { - return new DataImportEvent() - .id(EVENT_ID_01) - .tenant(TENANT_ID) - .eventType(DI_COMPLETED_TOPIC); - } - - private ProducerRecord newProducerRecord(String emittedEvent) { - return new ProducerRecord(getTopicName(TENANT_ID, DI_COMPLETED_TOPIC), 0, - EVENT_ID_01, emittedEvent, defaultKafkaHeaders()); - } - private void mapAndSave(String marc) { - marc2BibframeMapper.fromMarcJson(marc) - .map(resourceModelMapper::toEntity) - .map(resourceRepo::save) - .map(Resource::getOutgoingEdges) - .stream() - .flatMap(Set::stream) - .forEach(this::saveEdge); + tenantScopedExecutionService.execute(TENANT_ID, + () -> marc2BibframeMapper.fromMarcJson(marc) + .map(resourceModelMapper::toEntity) + .map(resourceRepo::save) + .map(Resource::getOutgoingEdges) + .stream() + .flatMap(Set::stream) + .forEach(this::saveEdge) + ); } private void saveEdge(ResourceEdge resourceEdge) { diff --git a/src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerTest.java b/src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerTest.java new file mode 100644 index 00000000..c01fb80e --- /dev/null +++ b/src/test/java/org/folio/linked/data/integration/kafka/listener/handler/SourceRecordDomainEventHandlerTest.java @@ -0,0 +1,174 @@ +package org.folio.linked.data.integration.kafka.listener.handler; + +import static org.folio.ld.dictionary.ResourceTypeDictionary.CONCEPT; +import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE; +import static org.folio.ld.dictionary.ResourceTypeDictionary.PERSON; +import static org.folio.linked.data.model.entity.ResourceSource.LINKED_DATA; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum.CREATED; +import static org.folio.search.domain.dto.SourceRecordType.MARC_AUTHORITY; +import static org.folio.search.domain.dto.SourceRecordType.MARC_BIB; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.folio.ld.dictionary.model.InstanceMetadata; +import org.folio.ld.dictionary.model.Resource; +import org.folio.linked.data.repo.InstanceMetadataRepository; +import org.folio.linked.data.service.resource.ResourceMarcService; +import org.folio.marc4ld.service.marc2ld.authority.MarcAuthority2ldMapper; +import org.folio.marc4ld.service.marc2ld.bib.MarcBib2ldMapper; +import org.folio.search.domain.dto.SourceRecordDomainEvent; +import org.folio.spring.testing.type.UnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@UnitTest +@ExtendWith(MockitoExtension.class) +public class SourceRecordDomainEventHandlerTest { + + @InjectMocks + private SourceRecordDomainEventHandler sourceRecordDomainEventHandler; + + @Mock + private MarcBib2ldMapper marcBib2ldMapper; + @Mock + private MarcAuthority2ldMapper marcAuthority2ldMapper; + @Mock + private ResourceMarcService resourceMarcService; + @Mock + private InstanceMetadataRepository instanceMetadataRepository; + + @Test + void shouldNotTriggerSaving_ifIncomingEventHasNoMarcInside() { + // given + var event = new SourceRecordDomainEvent().id("1"); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldNotTriggerSaving_ifIncomingEventContainsNotSupportedRecordType() { + // given + var event = new SourceRecordDomainEvent().id("2") + .eventPayload("{}"); + + // when + sourceRecordDomainEventHandler.handle(event, null); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldNotTriggerSaving_ifIncomingEventContainsNotSupportedEventType() { + // given + var event = new SourceRecordDomainEvent().id("3") + .eventPayload("{}"); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldNotTriggerSaving_ifResourceMappedOutOfIncomingEventIsEmpty() { + // given + var event = new SourceRecordDomainEvent().id("4") + .eventType(CREATED) + .eventPayload("{ \"key\": \"value\"}"); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldNotTriggerSaving_ifResourceMappedOutOfIncomingEventIsExistedByIdInstanceWithLinkedDataSource() { + // given + var event = new SourceRecordDomainEvent().id("5") + .eventType(CREATED) + .eventPayload("{ \"key\": \"value\"}"); + var mapped = new Resource().setId(4L).addType(INSTANCE); + doReturn(Optional.of(mapped)).when(marcBib2ldMapper).fromMarcJson(event.getEventPayload()); + var existedMetaData = + new org.folio.linked.data.model.entity.InstanceMetadata(new org.folio.linked.data.model.entity.Resource()) + .setSource(LINKED_DATA); + doReturn(Optional.of(existedMetaData)).when(instanceMetadataRepository).findById(mapped.getId()); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldNotTriggerSaving_ifResourceMappedOutOfIncomingEventIsExistedByInventoryIdInstanceWithLinkedDataSource() { + // given + var event = new SourceRecordDomainEvent().id("6") + .eventType(CREATED) + .eventPayload("{ \"key\": \"value\"}"); + var mapped = new Resource().setId(4L).addType(INSTANCE) + .setInstanceMetadata(new InstanceMetadata().setInventoryId(UUID.randomUUID().toString())); + doReturn(Optional.of(mapped)).when(marcBib2ldMapper).fromMarcJson(event.getEventPayload()); + var existedMetaData = + new org.folio.linked.data.model.entity.InstanceMetadata(new org.folio.linked.data.model.entity.Resource()) + .setSource(LINKED_DATA); + doReturn(Optional.of(existedMetaData)) + .when(instanceMetadataRepository).findByInventoryId(mapped.getInstanceMetadata().getInventoryId()); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verifyNoInteractions(resourceMarcService); + } + + @Test + void shouldTriggerResourceSaving_forCorrectMarcBibEvent() { + // given + var event = new SourceRecordDomainEvent().id("7") + .eventType(CREATED) + .eventPayload("{ \"key\": \"value\"}"); + var mapped = new Resource().setId(7L).addType(INSTANCE); + doReturn(Optional.of(mapped)).when(marcBib2ldMapper).fromMarcJson(event.getEventPayload()); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_BIB); + + // then + verify(resourceMarcService).saveMarcResource(mapped); + } + + @Test + void shouldTriggerResourceSaving_forCorrectMarcAuthorityEvent() { + // given + var event = new SourceRecordDomainEvent().id("8") + .eventType(CREATED) + .eventPayload("{ \"key\": \"value\"}"); + var mapped1 = new Resource().setId(9L).addType(PERSON); + var mapped2 = new Resource().setId(10L).addType(CONCEPT); + doReturn(List.of(mapped1, mapped2)).when(marcAuthority2ldMapper).fromMarcJson(event.getEventPayload()); + + // when + sourceRecordDomainEventHandler.handle(event, MARC_AUTHORITY); + + // then + verify(resourceMarcService).saveMarcResource(mapped1); + verify(resourceMarcService).saveMarcResource(mapped2); + } +} diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnitTest.java index ea1555df..381d8381 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/common/place/OriginPlaceMapperUnitTest.java @@ -12,7 +12,7 @@ import org.folio.linked.data.domain.dto.Place; import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/CarrierMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/CarrierMapperUnitTest.java index 71b15381..906f1799 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/CarrierMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/CarrierMapperUnitTest.java @@ -13,7 +13,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.mapper.dto.monograph.instance.sub.CarrierMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnitTest.java index bbc93d5e..b6d7abb9 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/ContentMapperUnitTest.java @@ -16,7 +16,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceTypeEntity; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnitTest.java index 2af54db2..30cd6c58 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/GovernmentPublicationMapperUnitTest.java @@ -12,7 +12,7 @@ import org.folio.linked.data.domain.dto.Category; import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/MediaMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/MediaMapperUnitTest.java index 038834ca..cdff09bc 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/MediaMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/MediaMapperUnitTest.java @@ -13,7 +13,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.mapper.dto.monograph.instance.sub.MediaMapperUnit; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnitTest.java b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnitTest.java index 057eec7d..4af64a67 100644 --- a/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnitTest.java +++ b/src/test/java/org/folio/linked/data/mapper/dto/monograph/work/sub/TargetAudienceMapperUnitTest.java @@ -16,7 +16,7 @@ import org.folio.linked.data.mapper.dto.common.CoreMapperImpl; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceTypeEntity; -import org.folio.linked.data.service.HashService; +import org.folio.linked.data.service.resource.hash.HashService; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; diff --git a/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java b/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java index 48f02df7..ee629306 100644 --- a/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java +++ b/src/test/java/org/folio/linked/data/mapper/kafka/inventory/InstanceIngressMessageMapperTest.java @@ -48,7 +48,8 @@ void testMapping() { // when assertThat(result) - .hasFieldOrPropertyWithValue("id", String.valueOf(instance.getId())) + .hasAllNullFieldsOrPropertiesExcept("id", "eventPayload") + .hasFieldOrProperty("id") .extracting("eventPayload") .hasFieldOrPropertyWithValue("sourceRecordIdentifier", inventoryId) .hasFieldOrPropertyWithValue("sourceType", InstanceIngressPayload.SourceTypeEnum.LINKED_DATA) diff --git a/src/test/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapperTest.java b/src/test/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapperTest.java index 512f3cac..4bafe6b9 100644 --- a/src/test/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapperTest.java +++ b/src/test/java/org/folio/linked/data/mapper/kafka/search/AuthoritySearchMessageMapperTest.java @@ -11,7 +11,7 @@ import org.folio.linked.data.mapper.kafka.search.identifier.IndexIdentifierMapper; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceEdge; -import org.folio.search.domain.dto.BibframeAuthorityIdentifiersInner; +import org.folio.search.domain.dto.LinkedDataAuthorityIdentifiersInner; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,7 +27,7 @@ class AuthoritySearchMessageMapperTest { private AuthoritySearchMessageMapperImpl mapper; @Mock - private IndexIdentifierMapper innerIndexIdentifierMapper; + private IndexIdentifierMapper innerIndexIdentifierMapper; @Test void toIndex_shouldMapResourceCorrectly() { @@ -41,8 +41,8 @@ void toIndex_shouldMapResourceCorrectly() { .addTypes(ID_LCCN); resource .addOutgoingEdge(new ResourceEdge(resource, id, MAP)); - var expectedIdentifiers = List.of(new BibframeAuthorityIdentifiersInner() - .type(BibframeAuthorityIdentifiersInner.TypeEnum.LCCN) + var expectedIdentifiers = List.of(new LinkedDataAuthorityIdentifiersInner() + .type(LinkedDataAuthorityIdentifiersInner.TypeEnum.LCCN) .value(randomLong().toString()) ); doReturn(expectedIdentifiers).when(innerIndexIdentifierMapper).extractIdentifiers(resource); @@ -52,7 +52,8 @@ void toIndex_shouldMapResourceCorrectly() { //then assertThat(result) - .hasFieldOrPropertyWithValue("id", String.valueOf(resource.getId())) + .hasAllNullFieldsOrPropertiesExcept("id", "resourceName", "_new") + .hasFieldOrProperty("id") .hasFieldOrPropertyWithValue("resourceName", "linked-data-authority") .extracting("_new") .hasFieldOrPropertyWithValue("id", String.valueOf(resource.getId())) diff --git a/src/test/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapperTest.java b/src/test/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapperTest.java index d6995157..3890e929 100644 --- a/src/test/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapperTest.java +++ b/src/test/java/org/folio/linked/data/mapper/kafka/search/BibliographicSearchMessageMapperTest.java @@ -19,22 +19,22 @@ import static org.folio.linked.data.test.MonographTestUtil.getSampleWork; import static org.folio.linked.data.test.TestUtil.getJsonNode; import static org.folio.linked.data.test.TestUtil.randomLong; -import static org.folio.search.domain.dto.BibframeContributorsInner.TypeEnum.FAMILY; -import static org.folio.search.domain.dto.BibframeContributorsInner.TypeEnum.MEETING; -import static org.folio.search.domain.dto.BibframeContributorsInner.TypeEnum.ORGANIZATION; -import static org.folio.search.domain.dto.BibframeContributorsInner.TypeEnum.PERSON; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum.EAN; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum.ISBN; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum.LCCN; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum.LOCAL_ID; -import static org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner.TypeEnum.UNKNOWN; +import static org.folio.search.domain.dto.LinkedDataWorkContributorsInner.TypeEnum.FAMILY; +import static org.folio.search.domain.dto.LinkedDataWorkContributorsInner.TypeEnum.MEETING; +import static org.folio.search.domain.dto.LinkedDataWorkContributorsInner.TypeEnum.ORGANIZATION; +import static org.folio.search.domain.dto.LinkedDataWorkContributorsInner.TypeEnum.PERSON; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.MAIN; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.MAIN_PARALLEL; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.MAIN_VARIANT; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.SUB; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.SUB_PARALLEL; import static org.folio.search.domain.dto.LinkedDataWorkIndexTitleType.SUB_VARIANT; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum.EAN; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum.ISBN; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum.LCCN; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum.LOCAL_ID; +import static org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner.TypeEnum.UNKNOWN; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; @@ -46,16 +46,16 @@ import org.folio.ld.dictionary.ResourceTypeDictionary; import org.folio.linked.data.mapper.dto.common.SingleResourceMapper; import org.folio.linked.data.mapper.dto.common.SingleResourceMapperUnit; -import org.folio.linked.data.mapper.kafka.search.identifier.BibframeInstancesInnerIdentifiersInnerMapperAbstract; import org.folio.linked.data.mapper.kafka.search.identifier.IndexIdentifierMapper; +import org.folio.linked.data.mapper.kafka.search.identifier.LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.model.entity.ResourceEdge; -import org.folio.search.domain.dto.BibframeContributorsInner; -import org.folio.search.domain.dto.BibframeInstancesInner; -import org.folio.search.domain.dto.BibframeInstancesInnerIdentifiersInner; -import org.folio.search.domain.dto.BibframeTitlesInner; import org.folio.search.domain.dto.LinkedDataWork; +import org.folio.search.domain.dto.LinkedDataWorkContributorsInner; import org.folio.search.domain.dto.LinkedDataWorkIndexTitleType; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInner; +import org.folio.search.domain.dto.LinkedDataWorkInstancesInnerIdentifiersInner; +import org.folio.search.domain.dto.LinkedDataWorkTitlesInner; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -75,8 +75,8 @@ class BibliographicSearchMessageMapperTest { @Mock private SingleResourceMapper singleResourceMapper; @Spy - private IndexIdentifierMapper innerIndexIdentifierMapper - = new BibframeInstancesInnerIdentifiersInnerMapperAbstract(); + private IndexIdentifierMapper innerIndexIdentifierMapper + = new LinkedDataWorkInstancesInnerIdentifiersInnerMapperAbstract(); @BeforeEach public void setupMocks() { @@ -106,7 +106,7 @@ void toIndex_shouldReturnCorrectlyMappedIndex_fromResourceWithIdOnly() { // then assertThat(result) .hasAllNullFieldsOrPropertiesExcept("id", "resourceName", "_new") - .hasFieldOrPropertyWithValue("id", String.valueOf(resource.getId())) + .hasFieldOrProperty("id") .hasFieldOrPropertyWithValue("resourceName", "linked-data-work") .extracting("_new") .isInstanceOf(LinkedDataWork.class); @@ -137,7 +137,7 @@ void toIndex_shouldReturnCorrectlyMappedIndex_fromWork() { // then assertThat(result) - .hasFieldOrPropertyWithValue("id", String.valueOf(work.getId())) + .hasFieldOrProperty("id") .hasFieldOrPropertyWithValue("resourceName", "linked-data-work") .extracting("_new") .isInstanceOf(LinkedDataWork.class); @@ -208,7 +208,7 @@ private void validateWork(LinkedDataWork result, Resource work, Resource wrongCo assertThat(result.getInstances()).hasSize(instancesExpected); } - private void validateInstance(BibframeInstancesInner instanceIndex, Resource instance) { + private void validateInstance(LinkedDataWorkInstancesInner instanceIndex, Resource instance) { assertThat(instanceIndex.getId()).isEqualTo(instance.getId().toString()); assertTitle(instanceIndex.getTitles().get(0), "Primary: mainTitle" + instance.getId(), MAIN); assertTitle(instanceIndex.getTitles().get(1), "Primary: subTitle", SUB); @@ -232,18 +232,18 @@ private void validateInstance(BibframeInstancesInner instanceIndex, Resource ins .isEqualTo(instance.getDoc().get(EDITION_STATEMENT.getValue()).get(0).asText()); } - private void assertTitle(BibframeTitlesInner titleInner, String value, LinkedDataWorkIndexTitleType type) { + private void assertTitle(LinkedDataWorkTitlesInner titleInner, String value, LinkedDataWorkIndexTitleType type) { assertThat(titleInner.getValue()).isEqualTo(value); assertThat(titleInner.getType()).isEqualTo(type); } - private void assertId(BibframeInstancesInnerIdentifiersInner idInner, String value, TypeEnum type) { + private void assertId(LinkedDataWorkInstancesInnerIdentifiersInner idInner, String value, TypeEnum type) { assertThat(idInner.getValue()).isEqualTo(value); assertThat(idInner.getType()).isEqualTo(type); } - private void assertContributor(BibframeContributorsInner contributorInner, String value, - BibframeContributorsInner.TypeEnum type, boolean isCreator) { + private void assertContributor(LinkedDataWorkContributorsInner contributorInner, String value, + LinkedDataWorkContributorsInner.TypeEnum type, boolean isCreator) { assertThat(contributorInner.getName()).isEqualTo(value); assertThat(contributorInner.getType()).isEqualTo(type); assertThat(contributorInner.getIsCreator()).isEqualTo(isCreator); diff --git a/src/test/java/org/folio/linked/data/service/impl/DictionaryServiceImplTest.java b/src/test/java/org/folio/linked/data/service/DictionaryServiceImplTest.java similarity index 98% rename from src/test/java/org/folio/linked/data/service/impl/DictionaryServiceImplTest.java rename to src/test/java/org/folio/linked/data/service/DictionaryServiceImplTest.java index 415b2c9d..e0401f76 100644 --- a/src/test/java/org/folio/linked/data/service/impl/DictionaryServiceImplTest.java +++ b/src/test/java/org/folio/linked/data/service/DictionaryServiceImplTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; diff --git a/src/test/java/org/folio/linked/data/service/impl/ProfileServiceImplTest.java b/src/test/java/org/folio/linked/data/service/ProfileServiceImplTest.java similarity index 97% rename from src/test/java/org/folio/linked/data/service/impl/ProfileServiceImplTest.java rename to src/test/java/org/folio/linked/data/service/ProfileServiceImplTest.java index 18b64f54..2a9d9036 100644 --- a/src/test/java/org/folio/linked/data/service/impl/ProfileServiceImplTest.java +++ b/src/test/java/org/folio/linked/data/service/ProfileServiceImplTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service; import static org.assertj.core.api.Assertions.assertThat; import static org.folio.linked.data.util.Constants.PROFILE_NOT_FOUND; diff --git a/src/test/java/org/folio/linked/data/service/impl/BatchIndexServiceImplTest.java b/src/test/java/org/folio/linked/data/service/index/BatchIndexServiceImplTest.java similarity index 95% rename from src/test/java/org/folio/linked/data/service/impl/BatchIndexServiceImplTest.java rename to src/test/java/org/folio/linked/data/service/index/BatchIndexServiceImplTest.java index fe937fee..828432fb 100644 --- a/src/test/java/org/folio/linked/data/service/impl/BatchIndexServiceImplTest.java +++ b/src/test/java/org/folio/linked/data/service/index/BatchIndexServiceImplTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl; +package org.folio.linked.data.service.index; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doNothing; @@ -10,7 +10,6 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.folio.linked.data.integration.kafka.sender.search.WorkCreateMessageSender; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.BatchIndexService; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/org/folio/linked/data/service/ReindexServiceTest.java b/src/test/java/org/folio/linked/data/service/index/ReindexServiceTest.java similarity index 93% rename from src/test/java/org/folio/linked/data/service/ReindexServiceTest.java rename to src/test/java/org/folio/linked/data/service/index/ReindexServiceTest.java index b60fa2a5..f017343c 100644 --- a/src/test/java/org/folio/linked/data/service/ReindexServiceTest.java +++ b/src/test/java/org/folio/linked/data/service/index/ReindexServiceTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.index; import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK; import static org.mockito.ArgumentMatchers.any; @@ -11,8 +11,8 @@ import java.util.stream.Stream; import org.folio.linked.data.model.entity.Resource; import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.BatchIndexService.BatchIndexResult; -import org.folio.linked.data.service.impl.ReindexServiceImpl; +import org.folio.linked.data.service.index.BatchIndexService.BatchIndexResult; +import org.folio.linked.data.service.resource.ResourceService; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/folio/linked/data/service/resource/ResourceGraphServiceTest.java b/src/test/java/org/folio/linked/data/service/resource/ResourceGraphServiceTest.java new file mode 100644 index 00000000..ce9f22f6 --- /dev/null +++ b/src/test/java/org/folio/linked/data/service/resource/ResourceGraphServiceTest.java @@ -0,0 +1,69 @@ +package org.folio.linked.data.service.resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.linked.data.test.TestUtil.randomLong; +import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; +import static org.folio.linked.data.util.Constants.RESOURCE_WITH_GIVEN_ID; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import org.folio.linked.data.domain.dto.ResourceGraphDto; +import org.folio.linked.data.exception.NotFoundException; +import org.folio.linked.data.mapper.dto.ResourceDtoMapper; +import org.folio.linked.data.model.entity.Resource; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class ResourceGraphServiceTest { + + @InjectMocks + private ResourceGraphServiceImpl resourceGraphService; + + @Mock + private ResourceRepository resourceRepo; + @Mock + private ResourceEdgeRepository edgeRepo; + @Mock + private ResourceDtoMapper resourceDtoMapper; + + @Test + void getResourceGraph_shouldReturnResourceGraphDto_whenResourceExists() { + //given + var id = randomLong(); + var resource = new Resource().setId(id); + var expectedResourceGraphDto = new ResourceGraphDto().id(String.valueOf(id)); + + when(resourceRepo.findById(id)).thenReturn(Optional.of(resource)); + when(resourceDtoMapper.toResourceGraphDto(resource)).thenReturn(expectedResourceGraphDto); + + //when + var resourceGraphDto = resourceGraphService.getResourceGraph(id); + + //then + assertThat(expectedResourceGraphDto).isEqualTo(resourceGraphDto); + } + + @Test + void getResourceGraph_shouldThrowNotFoundException_whenResourceDoesNotExist() { + // given + var id = randomLong(); + + when(resourceRepo.findById(id)).thenReturn(Optional.empty()); + + // when + var thrown = assertThrows(NotFoundException.class, () -> resourceGraphService.getResourceGraph(id)); + + // then + assertThat(thrown.getMessage()).isEqualTo(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND); + } + +} diff --git a/src/test/java/org/folio/linked/data/service/resource/ResourceMarcServiceTest.java b/src/test/java/org/folio/linked/data/service/resource/ResourceMarcServiceTest.java new file mode 100644 index 00000000..68810a64 --- /dev/null +++ b/src/test/java/org/folio/linked/data/service/resource/ResourceMarcServiceTest.java @@ -0,0 +1,178 @@ +package org.folio.linked.data.service.resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.folio.linked.data.test.MonographTestUtil.getSampleInstanceResource; +import static org.folio.linked.data.test.MonographTestUtil.getSampleWork; +import static org.folio.linked.data.test.TestUtil.random; +import static org.folio.linked.data.test.TestUtil.randomLong; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.UUID; +import org.folio.ld.dictionary.model.InstanceMetadata; +import org.folio.linked.data.domain.dto.ResourceMarcViewDto; +import org.folio.linked.data.exception.NotFoundException; +import org.folio.linked.data.exception.ValidationException; +import org.folio.linked.data.mapper.ResourceModelMapper; +import org.folio.linked.data.mapper.dto.ResourceDtoMapper; +import org.folio.linked.data.model.entity.Resource; +import org.folio.linked.data.model.entity.event.ResourceCreatedEvent; +import org.folio.linked.data.model.entity.event.ResourceReplacedEvent; +import org.folio.linked.data.model.entity.event.ResourceUpdatedEvent; +import org.folio.linked.data.repo.InstanceMetadataRepository; +import org.folio.linked.data.repo.ResourceEdgeRepository; +import org.folio.linked.data.repo.ResourceRepository; +import org.folio.marc4ld.service.ld2marc.Bibframe2MarcMapper; +import org.folio.spring.testing.type.UnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class ResourceMarcServiceTest { + + @InjectMocks + private ResourceMarcServiceImpl resourceMarcService; + + @Mock + private InstanceMetadataRepository instanceMetadataRepo; + @Mock + private ResourceRepository resourceRepo; + @Mock + private ResourceEdgeRepository edgeRepo; + @Mock + private ResourceDtoMapper resourceDtoMapper; + @Mock + private ResourceModelMapper resourceModelMapper; + @Mock + private Bibframe2MarcMapper bibframe2MarcMapper; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + @Mock + private ResourceGraphService resourceGraphService; + + @Test + void getResourceMarcView_shouldReturnExistedEntity() { + // given + var id = randomLong(); + var existedResource = getSampleInstanceResource(); + var expectedModelResource = random(org.folio.ld.dictionary.model.Resource.class); + var expectedMarcString = "{mark: \"json\"}"; + var expectedResponse = random(ResourceMarcViewDto.class); + + when(resourceRepo.findById(id)) + .thenReturn(Optional.of(existedResource)); + when(resourceModelMapper.toModel(existedResource)) + .thenReturn(expectedModelResource); + when(bibframe2MarcMapper.toMarcJson(expectedModelResource)) + .thenReturn(expectedMarcString); + when(resourceDtoMapper.toMarcViewDto(existedResource, expectedMarcString)) + .thenReturn(expectedResponse); + + // when + var result = resourceMarcService.getResourceMarcView(id); + + // then + assertThat(result) + .isEqualTo(expectedResponse); + } + + @Test + void getResourceMarcView_shouldThrowNotFoundException_ifNoEntityExists() { + // given + var notExistedId = randomLong(); + when(resourceRepo.findById(notExistedId)) + .thenReturn(Optional.empty()); + + // when + assertThatExceptionOfType(NotFoundException.class) + .isThrownBy(() -> resourceMarcService.getResourceMarcView(notExistedId)); + } + + @Test + void getResourceMarcView_shouldThrowException_ifNotInstance() { + // given + var notExistedId = randomLong(); + var existedResource = getSampleWork(null); + + when(resourceRepo.findById(notExistedId)) + .thenReturn(Optional.of(existedResource)); + + // when + assertThatExceptionOfType(ValidationException.class) + .isThrownBy(() -> resourceMarcService.getResourceMarcView(notExistedId)); + } + + @Test + void saveMarcResource_shouldCreateNewResource_ifGivenModelDoesNotExistsByIdAndInventoryId() { + // given + var id = randomLong(); + var model = new org.folio.ld.dictionary.model.Resource().setId(id); + var mapped = new Resource().setId(id); + doReturn(mapped).when(resourceModelMapper).toEntity(model); + doReturn(false).when(resourceRepo).existsById(id); + doReturn(mapped).when(resourceGraphService).saveMergingGraph(mapped); + + // when + var result = resourceMarcService.saveMarcResource(model); + + // then + assertThat(result).isEqualTo(id); + verify(resourceGraphService).saveMergingGraph(mapped); + verify(applicationEventPublisher).publishEvent(new ResourceCreatedEvent(mapped)); + } + + @Test + void saveMarcResource_shouldUpdateResource_ifGivenModelExistsById() { + // given + var id = randomLong(); + var model = new org.folio.ld.dictionary.model.Resource().setId(id); + var mapped = new Resource().setId(id); + doReturn(mapped).when(resourceModelMapper).toEntity(model); + doReturn(true).when(resourceRepo).existsById(id); + doReturn(mapped).when(resourceGraphService).saveMergingGraph(mapped); + + // when + var result = resourceMarcService.saveMarcResource(model); + + // then + assertThat(result).isEqualTo(id); + verify(resourceGraphService).saveMergingGraph(mapped); + verify(applicationEventPublisher).publishEvent(new ResourceUpdatedEvent(mapped)); + } + + @Test + void saveMarcResource_shouldReplaceResource_ifGivenModelExistsByInventoryIdButNotById() { + // given + var id = randomLong(); + var invId = UUID.randomUUID().toString(); + var existed = new Resource().setId(id).setManaged(true); + doReturn(Optional.of(existed)).when(resourceRepo).findByInstanceMetadataInventoryId(invId); + var existedMetadata = new org.folio.linked.data.model.entity.InstanceMetadata(existed).setInventoryId(invId); + doReturn(Optional.of(existedMetadata)).when(instanceMetadataRepo).findById(id); + var model = new org.folio.ld.dictionary.model.Resource() + .setId(id) + .setInstanceMetadata(new InstanceMetadata().setInventoryId(invId)); + var mapped = new Resource().setId(id); + doReturn(mapped).when(resourceModelMapper).toEntity(model); + doReturn(false).when(resourceRepo).existsById(id); + + doReturn(mapped).when(resourceGraphService).saveMergingGraph(mapped); + + // when + var result = resourceMarcService.saveMarcResource(model); + + // then + assertThat(result).isEqualTo(id); + verify(resourceGraphService).saveMergingGraph(mapped); + verify(applicationEventPublisher).publishEvent(new ResourceReplacedEvent(existed, mapped)); + } + +} diff --git a/src/test/java/org/folio/linked/data/service/ResourceServiceTest.java b/src/test/java/org/folio/linked/data/service/resource/ResourceServiceTest.java similarity index 75% rename from src/test/java/org/folio/linked/data/service/ResourceServiceTest.java rename to src/test/java/org/folio/linked/data/service/resource/ResourceServiceTest.java index 41ee9196..874569c7 100644 --- a/src/test/java/org/folio/linked/data/service/ResourceServiceTest.java +++ b/src/test/java/org/folio/linked/data/service/resource/ResourceServiceTest.java @@ -1,12 +1,10 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.resource; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.folio.ld.dictionary.PredicateDictionary.INSTANTIATES; import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE; import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK; import static org.folio.linked.data.test.MonographTestUtil.getSampleInstanceResource; -import static org.folio.linked.data.test.MonographTestUtil.getSampleWork; import static org.folio.linked.data.test.TestUtil.random; import static org.folio.linked.data.test.TestUtil.randomLong; import static org.folio.linked.data.util.Constants.IS_NOT_FOUND; @@ -26,8 +24,6 @@ import org.folio.linked.data.domain.dto.InstanceRequest; import org.folio.linked.data.domain.dto.InstanceResponse; import org.folio.linked.data.domain.dto.InstanceResponseField; -import org.folio.linked.data.domain.dto.ResourceGraphDto; -import org.folio.linked.data.domain.dto.ResourceMarcViewDto; import org.folio.linked.data.domain.dto.ResourceRequestDto; import org.folio.linked.data.domain.dto.ResourceResponseDto; import org.folio.linked.data.domain.dto.ResourceShort; @@ -37,8 +33,6 @@ import org.folio.linked.data.domain.dto.WorkResponse; import org.folio.linked.data.domain.dto.WorkResponseField; import org.folio.linked.data.exception.NotFoundException; -import org.folio.linked.data.exception.ValidationException; -import org.folio.linked.data.mapper.ResourceModelMapper; import org.folio.linked.data.mapper.dto.ResourceDtoMapper; import org.folio.linked.data.model.ResourceShortInfo; import org.folio.linked.data.model.entity.Resource; @@ -50,9 +44,7 @@ import org.folio.linked.data.repo.InstanceMetadataRepository; import org.folio.linked.data.repo.ResourceEdgeRepository; import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.impl.ResourceServiceImpl; import org.folio.linked.data.service.resource.meta.MetadataService; -import org.folio.marc4ld.service.ld2marc.Bibframe2MarcMapper; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -82,13 +74,11 @@ class ResourceServiceTest { @Mock private ResourceDtoMapper resourceDtoMapper; @Mock - private ResourceModelMapper resourceModelMapper; - @Mock - private Bibframe2MarcMapper bibframe2MarcMapper; - @Mock private ApplicationEventPublisher applicationEventPublisher; @Mock private MetadataService metadataService; + @Mock + private ResourceGraphService resourceGraphService; @Test void create_shouldPersistMappedResourceAndNotPublishResourceCreatedEvent_forResourceWithNoWork() { @@ -97,10 +87,10 @@ void create_shouldPersistMappedResourceAndNotPublishResourceCreatedEvent_forReso var mapped = new Resource().setId(12345L); when(resourceDtoMapper.toEntity(request)).thenReturn(mapped); var persisted = new Resource().setId(67890L); - when(resourceRepo.save(mapped)).thenReturn(persisted); var expectedResponse = new ResourceResponseDto(); expectedResponse.setResource(new InstanceResponseField().instance(new InstanceResponse().id("123"))); - when(resourceDtoMapper.toDto(mapped)).thenReturn(expectedResponse); + when(resourceDtoMapper.toDto(persisted)).thenReturn(expectedResponse); + when(resourceGraphService.saveMergingGraph(mapped)).thenReturn(persisted); // when var response = resourceService.createResource(request); @@ -116,10 +106,10 @@ void create_shouldPersistMappedResourceAndPublishResourceCreatedEvent_forResourc var request = new ResourceRequestDto(); var work = new Resource().addTypes(WORK).setId(555L); when(resourceDtoMapper.toEntity(request)).thenReturn(work); - when(resourceRepo.save(work)).thenReturn(work); var expectedResponse = new ResourceResponseDto(); expectedResponse.setResource(new InstanceResponseField().instance(new InstanceResponse().id("123"))); when(resourceDtoMapper.toDto(work)).thenReturn(expectedResponse); + when(resourceGraphService.saveMergingGraph(work)).thenReturn(work); // when var response = resourceService.createResource(request); @@ -137,10 +127,10 @@ void create_shouldPersistMappedResourceAndPublishResourceCreatedEvent_forResourc var request = new ResourceRequestDto(); var work = new Resource().addTypes(WORK).setId(444L); when(resourceDtoMapper.toEntity(request)).thenReturn(work); - when(resourceRepo.save(work)).thenReturn(work); var expectedResponse = new ResourceResponseDto(); expectedResponse.setResource(new InstanceResponseField().instance(new InstanceResponse().id("123"))); when(resourceDtoMapper.toDto(work)).thenReturn(expectedResponse); + when(resourceGraphService.saveMergingGraph(work)).thenReturn(work); // when var response = resourceService.createResource(request); @@ -189,7 +179,7 @@ void getResourceIdByInventoryId_shouldReturnIdOfExistedEntity() { // given var inventoryId = UUID.randomUUID().toString(); var existedResource = getSampleInstanceResource(); - when(instanceMetadataRepo.findByInventoryId(inventoryId)).thenReturn(Optional.of(existedResource::getId)); + when(instanceMetadataRepo.findIdByInventoryId(inventoryId)).thenReturn(Optional.of(existedResource::getId)); // when var result = resourceService.getResourceIdByInventoryId(inventoryId); @@ -202,7 +192,6 @@ void getResourceIdByInventoryId_shouldReturnIdOfExistedEntity() { void getResourceIdByInventoryId_shouldThrowNotFoundException_ifNoEntityExistsWithGivenInventoryId() { // given var inventoryId = UUID.randomUUID().toString(); - when(instanceMetadataRepo.findByInventoryId(inventoryId)).thenReturn(Optional.empty()); // when var thrown = assertThrows( @@ -266,19 +255,19 @@ void update_shouldSaveUpdatedResourceAndSendResourceUpdatedEvent_forResourceWith when(resourceRepo.findById(id)).thenReturn(Optional.of(oldWork)); var work = new Resource().setId(id).setLabel("saved").addTypes(WORK); when(resourceDtoMapper.toEntity(workDto)).thenReturn(work); - when(resourceRepo.save(work)).thenReturn(work); var expectedDto = new ResourceResponseDto().resource( new WorkResponseField().work(new WorkResponse().id(id.toString())) ); when(resourceDtoMapper.toDto(work)).thenReturn(expectedDto); + when(resourceGraphService.saveMergingGraph(work)).thenReturn(work); // when var result = resourceService.updateResource(id, workDto); // then assertThat(expectedDto).isEqualTo(result); - verify(resourceRepo).delete(oldWork); - verify(resourceRepo).save(work); + verify(resourceGraphService).breakEdgesAndDelete(oldWork); + verify(resourceGraphService).saveMergingGraph(work); verify(applicationEventPublisher).publishEvent(new ResourceUpdatedEvent(work)); } @@ -293,20 +282,20 @@ void update_shouldSaveUpdatedResourceAndSendReplaceEvent_forResourceWithDifferen var instanceDto = new ResourceRequestDto().resource(new InstanceField().instance(new InstanceRequest())); when(resourceDtoMapper.toEntity(instanceDto)).thenReturn(mapped); - var persisted = new Resource().setId(oldId).setLabel("saved"); - when(resourceRepo.save(mapped)).thenReturn(persisted); + var persisted = new Resource().setId(newId).setLabel("saved"); var expectedDto = new ResourceResponseDto().resource( - new InstanceResponseField().instance(new InstanceResponse().id(oldId.toString())) + new InstanceResponseField().instance(new InstanceResponse().id(newId.toString())) ); - when(resourceDtoMapper.toDto(mapped)).thenReturn(expectedDto); + when(resourceDtoMapper.toDto(persisted)).thenReturn(expectedDto); + when(resourceGraphService.saveMergingGraph(mapped)).thenReturn(persisted); // when var result = resourceService.updateResource(oldId, instanceDto); // then assertThat(expectedDto).isEqualTo(result); - verify(resourceRepo).delete(oldInstance); - verify(resourceRepo).save(mapped); + verify(resourceGraphService).breakEdgesAndDelete(oldInstance); + verify(resourceGraphService).saveMergingGraph(mapped); verify(applicationEventPublisher).publishEvent(new ResourceReplacedEvent(oldInstance, mapped)); } @@ -320,7 +309,7 @@ void delete_shouldDeleteWorkAndPublishResourceDeletedEvent() { resourceService.deleteResource(work.getId()); // then - verify(resourceRepo).delete(work); + verify(resourceGraphService).breakEdgesAndDelete(work); var resourceDeletedEventCaptor = ArgumentCaptor.forClass(ResourceDeletedEvent.class); verify(applicationEventPublisher).publishEvent(resourceDeletedEventCaptor.capture()); assertThat(work).isEqualTo(resourceDeletedEventCaptor.getValue().resource()); @@ -344,94 +333,10 @@ void delete_shouldDeleteInstanceAndPublishResourceDeletedEvent() { resourceService.deleteResource(instance.getId()); // then - verify(resourceRepo).delete(instance); + verify(resourceGraphService).breakEdgesAndDelete(instance); var resourceDeletedEventCaptor = ArgumentCaptor.forClass(ResourceDeletedEvent.class); verify(applicationEventPublisher).publishEvent(resourceDeletedEventCaptor.capture()); assertThat(instance).isEqualTo(resourceDeletedEventCaptor.getValue().resource()); } - - @Test - void getResourceMarcViewById_shouldReturnExistedEntity() { - // given - var id = randomLong(); - var existedResource = getSampleInstanceResource(); - var expectedModelResource = random(org.folio.ld.dictionary.model.Resource.class); - var expectedMarcString = "{mark: \"json\"}"; - var expectedResponse = random(ResourceMarcViewDto.class); - - when(resourceRepo.findById(id)) - .thenReturn(Optional.of(existedResource)); - when(resourceModelMapper.toModel(existedResource)) - .thenReturn(expectedModelResource); - when(bibframe2MarcMapper.toMarcJson(expectedModelResource)) - .thenReturn(expectedMarcString); - when(resourceDtoMapper.toMarcViewDto(existedResource, expectedMarcString)) - .thenReturn(expectedResponse); - - // when - var result = resourceService.getResourceMarcViewById(id); - - // then - assertThat(result) - .isEqualTo(expectedResponse); - } - - @Test - void getResourceMarcViewById_shouldThrowNotFoundException_ifNoEntityExists() { - // given - var notExistedId = randomLong(); - when(resourceRepo.findById(notExistedId)) - .thenReturn(Optional.empty()); - - // when - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> resourceService.getResourceMarcViewById(notExistedId)); - } - - @Test - void getResourceMarcViewById_shouldThrowException_ifNotInstance() { - // given - var notExistedId = randomLong(); - var existedResource = getSampleWork(null); - - when(resourceRepo.findById(notExistedId)) - .thenReturn(Optional.of(existedResource)); - - // when - assertThatExceptionOfType(ValidationException.class) - .isThrownBy(() -> resourceService.getResourceMarcViewById(notExistedId)); - } - - @Test - void getResourceGraphById_shouldReturnResourceGraphDto_whenResourceExists() { - //given - var id = randomLong(); - var resource = new Resource().setId(id); - var expectedResourceGraphDto = new ResourceGraphDto().id(String.valueOf(id)); - - when(resourceRepo.findById(id)).thenReturn(Optional.of(resource)); - when(resourceDtoMapper.toResourceGraphDto(resource)).thenReturn(expectedResourceGraphDto); - - //when - var resourceGraphDto = resourceService.getResourceGraphById(id); - - //then - assertThat(expectedResourceGraphDto).isEqualTo(resourceGraphDto); - } - - @Test - void getResourceGraphById_shouldThrowNotFoundException_whenResourceDoesNotExist() { - // given - var id = randomLong(); - - when(resourceRepo.findById(id)).thenReturn(Optional.empty()); - - // when - var thrown = assertThrows(NotFoundException.class, () -> resourceService.getResourceGraphById(id)); - - // then - assertThat(thrown.getMessage()).isEqualTo(RESOURCE_WITH_GIVEN_ID + id + IS_NOT_FOUND); - } - } diff --git a/src/test/java/org/folio/linked/data/service/HashServiceTest.java b/src/test/java/org/folio/linked/data/service/resource/hash/HashServiceTest.java similarity index 94% rename from src/test/java/org/folio/linked/data/service/HashServiceTest.java rename to src/test/java/org/folio/linked/data/service/resource/hash/HashServiceTest.java index 79eaa4ad..d8ba29cc 100644 --- a/src/test/java/org/folio/linked/data/service/HashServiceTest.java +++ b/src/test/java/org/folio/linked/data/service/resource/hash/HashServiceTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service; +package org.folio.linked.data.service.resource.hash; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.folio.linked.data.test.TestUtil.randomLong; @@ -8,7 +8,6 @@ import org.folio.ld.fingerprint.service.FingerprintHashService; import org.folio.linked.data.mapper.ResourceModelMapper; import org.folio.linked.data.model.entity.Resource; -import org.folio.linked.data.service.impl.HashServiceImpl; import org.folio.spring.testing.type.UnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/org/folio/linked/data/service/impl/tenant/LinkedTenantServiceTest.java b/src/test/java/org/folio/linked/data/service/tenant/LinkedTenantServiceTest.java similarity index 93% rename from src/test/java/org/folio/linked/data/service/impl/tenant/LinkedTenantServiceTest.java rename to src/test/java/org/folio/linked/data/service/tenant/LinkedTenantServiceTest.java index 5eee18f9..70f7f8ff 100644 --- a/src/test/java/org/folio/linked/data/service/impl/tenant/LinkedTenantServiceTest.java +++ b/src/test/java/org/folio/linked/data/service/tenant/LinkedTenantServiceTest.java @@ -1,10 +1,11 @@ -package org.folio.linked.data.service.impl.tenant; +package org.folio.linked.data.service.tenant; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; +import org.folio.linked.data.service.tenant.worker.TenantServiceWorker; import org.folio.spring.FolioExecutionContext; import org.folio.spring.liquibase.FolioSpringLiquibase; import org.folio.spring.testing.type.UnitTest; diff --git a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorkerTest.java b/src/test/java/org/folio/linked/data/service/tenant/worker/DictionaryWorkerTest.java similarity index 93% rename from src/test/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorkerTest.java rename to src/test/java/org/folio/linked/data/service/tenant/worker/DictionaryWorkerTest.java index 15fa7220..ddda34ec 100644 --- a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/DictionaryWorkerTest.java +++ b/src/test/java/org/folio/linked/data/service/tenant/worker/DictionaryWorkerTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorkerTest.java b/src/test/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorkerTest.java similarity index 94% rename from src/test/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorkerTest.java rename to src/test/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorkerTest.java index 2c0706d5..d34d0929 100644 --- a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/KafkaAdminWorkerTest.java +++ b/src/test/java/org/folio/linked/data/service/tenant/worker/KafkaAdminWorkerTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorkerTest.java b/src/test/java/org/folio/linked/data/service/tenant/worker/SearchWorkerTest.java similarity index 94% rename from src/test/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorkerTest.java rename to src/test/java/org/folio/linked/data/service/tenant/worker/SearchWorkerTest.java index dc0681c5..70bc6868 100644 --- a/src/test/java/org/folio/linked/data/service/impl/tenant/workers/SearchWorkerTest.java +++ b/src/test/java/org/folio/linked/data/service/tenant/worker/SearchWorkerTest.java @@ -1,4 +1,4 @@ -package org.folio.linked.data.service.impl.tenant.workers; +package org.folio.linked.data.service.tenant.worker; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/src/test/java/org/folio/linked/data/test/ResourceTestRepository.java b/src/test/java/org/folio/linked/data/test/ResourceTestRepository.java new file mode 100644 index 00000000..2db1ef7c --- /dev/null +++ b/src/test/java/org/folio/linked/data/test/ResourceTestRepository.java @@ -0,0 +1,21 @@ +package org.folio.linked.data.test; + +import java.util.Set; +import org.folio.linked.data.model.entity.Resource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ResourceTestRepository extends JpaRepository { + + @Query("SELECT r FROM Resource r " + + "JOIN FETCH r.types t " + + "LEFT JOIN FETCH r.incomingEdges " + + "LEFT JOIN FETCH r.outgoingEdges " + + "WHERE t.uri IN :types") + Page findAllByTypeWithEdgesLoaded(@Param("types") Set types, Pageable pageable); +} diff --git a/src/test/java/org/folio/linked/data/test/ResourceTestService.java b/src/test/java/org/folio/linked/data/test/ResourceTestService.java index f761eab9..cce03282 100644 --- a/src/test/java/org/folio/linked/data/test/ResourceTestService.java +++ b/src/test/java/org/folio/linked/data/test/ResourceTestService.java @@ -8,7 +8,7 @@ import org.folio.linked.data.model.entity.pk.ResourceEdgePk; import org.folio.linked.data.repo.ResourceEdgeRepository; import org.folio.linked.data.repo.ResourceRepository; -import org.folio.linked.data.service.ResourceService; +import org.folio.linked.data.service.resource.ResourceGraphService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,7 +23,7 @@ public class ResourceTestService { @Autowired private ResourceEdgeRepository edgeRepository; @Autowired - private ResourceService resourceService; + private ResourceGraphService resourceGraphService; /** * Retrieves a resource by its unique identifier along with its associated edges up to a specified depth. @@ -50,7 +50,7 @@ private void fetchEdges(Resource resource, int edgesDepth) { } public Resource saveGraph(Resource resource) { - return resourceService.saveMergingGraph(resource); + return resourceGraphService.saveMergingGraph(resource); } public Optional findById(long id) { diff --git a/src/test/java/org/folio/linked/data/test/TestUtil.java b/src/test/java/org/folio/linked/data/test/TestUtil.java index a326cf67..54e7f347 100644 --- a/src/test/java/org/folio/linked/data/test/TestUtil.java +++ b/src/test/java/org/folio/linked/data/test/TestUtil.java @@ -44,6 +44,7 @@ public class TestUtil { public static final String FOLIO_TEST_PROFILE = "test-folio"; public static final String TENANT_ID = "test_tenant"; + public static final String RECORD_DOMAIN_EVENT_TOPIC = "srs.source_records"; public static final ObjectMapper OBJECT_MAPPER = new ObjectMapperConfig().objectMapper(); public static final String INSTANCE_WITH_WORK_REF_SAMPLE = loadResourceAsString("samples/instance_and_work_ref.json"); public static final String WORK_WITH_INSTANCE_REF_SAMPLE = loadResourceAsString("samples/work_and_instance_ref.json"); diff --git a/src/test/java/org/folio/linked/data/test/kafka/KafkaEventsTestDataFixture.java b/src/test/java/org/folio/linked/data/test/kafka/KafkaEventsTestDataFixture.java index 5cd8b401..b16ef636 100644 --- a/src/test/java/org/folio/linked/data/test/kafka/KafkaEventsTestDataFixture.java +++ b/src/test/java/org/folio/linked/data/test/kafka/KafkaEventsTestDataFixture.java @@ -1,55 +1,46 @@ package org.folio.linked.data.test.kafka; import static org.folio.linked.data.test.TestUtil.OBJECT_MAPPER; +import static org.folio.linked.data.test.TestUtil.RECORD_DOMAIN_EVENT_TOPIC; +import static org.folio.linked.data.test.TestUtil.TENANT_ID; +import static org.folio.linked.data.test.TestUtil.defaultKafkaHeaders; +import static org.folio.search.domain.dto.SourceRecordDomainEvent.EventTypeEnum; +import static org.folio.spring.tools.kafka.KafkaUtils.getTenantTopicName; +import java.util.ArrayList; import java.util.Map; import lombok.SneakyThrows; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.folio.search.domain.dto.SourceRecordDomainEvent; +import org.folio.search.domain.dto.SourceRecordType; public class KafkaEventsTestDataFixture { @SneakyThrows - public static String instanceCreatedEvent(String eventId, String tenantId, String marc) { - Map marcBib = Map.of( - "parsedRecord", Map.of( - "content", marc + public static ProducerRecord getSrsDomainEventProducerRecord(String id, + String marc, + EventTypeEnum type, + SourceRecordType recordType) { + var topic = getTenantTopicName(RECORD_DOMAIN_EVENT_TOPIC, TENANT_ID); + var value = OBJECT_MAPPER.writeValueAsString(Map.of( + "id", id, + "eventType", type, + "eventPayload", marc ) ); - Map eventPayload = Map.of( - "eventType", "DI_COMPLETED", - "tenant", tenantId, - "context", Map.of( - "MARC_BIBLIOGRAPHIC", OBJECT_MAPPER.writeValueAsString(marcBib), - "CURRENT_EVENT_TYPE", "DI_INVENTORY_INSTANCE_CREATED" - ) - ); - - return dataImportEvent(eventId, eventPayload); + var headers = new ArrayList<>(defaultKafkaHeaders()); + headers.add(new RecordHeader("folio.srs.recordType", recordType.name().getBytes())); + return new ProducerRecord(topic, 0, id, value, headers); } - @SneakyThrows - public static String authorityEvent(String eventId, String tenantId, String marc) { - Map marcBib = Map.of( - "parsedRecord", Map.of( - "content", marc - ) - ); - Map eventPayload = Map.of( - "eventType", "DI_COMPLETED", - "tenant", tenantId, - "context", Map.of( - "MARC_AUTHORITY", OBJECT_MAPPER.writeValueAsString(marcBib) - ) - ); - return dataImportEvent(eventId, eventPayload); + public static SourceRecordDomainEvent getSrsDomainEvent(String id, + String marc, + EventTypeEnum type) { + return new SourceRecordDomainEvent() + .id(id) + .eventType(type) + .eventPayload(marc); } - @SneakyThrows - public static String dataImportEvent(String eventId, Map eventPayload) { - Map event = Map.of( - "id", eventId, - "eventPayload", OBJECT_MAPPER.writeValueAsString(eventPayload) - ); - - return OBJECT_MAPPER.writeValueAsString(event); - } } diff --git a/src/test/java/org/folio/linked/data/util/KafkaUtilsTest.java b/src/test/java/org/folio/linked/data/util/KafkaUtilsTest.java new file mode 100644 index 00000000..a7b3541c --- /dev/null +++ b/src/test/java/org/folio/linked/data/util/KafkaUtilsTest.java @@ -0,0 +1,47 @@ +package org.folio.linked.data.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.UUID; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.apache.kafka.common.header.internals.RecordHeaders; +import org.folio.spring.testing.type.UnitTest; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +@UnitTest +public class KafkaUtilsTest { + + @Test + public void getHeaderValueByName_shouldReturnEmptyOptional_ifMessageContainsNoExpectedHeader() { + // given + var headerKey = "headerKey"; + var consumerRecord = new ConsumerRecord<>("topic", 1, 1, "key", "value"); + + // when + var result = KafkaUtils.getHeaderValueByName(consumerRecord, headerKey); + + // then + assertThat(result).isNotPresent(); + } + + @Test + public void getHeaderValueByName_shouldReturnOptionalWithHeader_ifMessageContainsExpectedHeader() { + // given + var headerKey = "headerKey"; + var headerValue = UUID.randomUUID().toString(); + var consumerRecord = new ConsumerRecord<>("topic", 1, 1, "key", "value"); + var headers = new RecordHeaders(List.of(new RecordHeader(headerKey, headerValue.getBytes()))); + ReflectionTestUtils.setField(consumerRecord, "headers", headers); + + // when + var result = KafkaUtils.getHeaderValueByName(consumerRecord, headerKey); + + // then + assertThat(result) + .isPresent() + .contains(headerValue); + } +} diff --git a/src/test/resources/application-test-folio.yml b/src/test/resources/application-test-folio.yml index 036d9706..882b53ae 100644 --- a/src/test/resources/application-test-folio.yml +++ b/src/test/resources/application-test-folio.yml @@ -16,7 +16,7 @@ folio: numPartitions: 1 - name: linked-data.instance-ingress numPartitions: 1 - - name: DI_COMPLETED + - name: srs.source_records num-partitions: 1 test: diff --git a/src/test/resources/samples/authority_100.jsonl b/src/test/resources/samples/marc2ld/authority_100.jsonl similarity index 100% rename from src/test/resources/samples/authority_100.jsonl rename to src/test/resources/samples/marc2ld/authority_100.jsonl diff --git a/src/test/resources/integration/kafka/search/expected_message.json b/src/test/resources/samples/marc2ld/expected_message.json similarity index 100% rename from src/test/resources/integration/kafka/search/expected_message.json rename to src/test/resources/samples/marc2ld/expected_message.json diff --git a/src/test/resources/samples/full_marc_sample.jsonl b/src/test/resources/samples/marc2ld/full_marc_sample.jsonl similarity index 100% rename from src/test/resources/samples/full_marc_sample.jsonl rename to src/test/resources/samples/marc2ld/full_marc_sample.jsonl diff --git a/src/test/resources/samples/marc_monograph_leader.jsonl b/src/test/resources/samples/marc2ld/marc_monograph_leader.jsonl similarity index 100% rename from src/test/resources/samples/marc_monograph_leader.jsonl rename to src/test/resources/samples/marc2ld/marc_monograph_leader.jsonl diff --git a/src/test/resources/samples/marc_non_monograph_leader.jsonl b/src/test/resources/samples/marc2ld/marc_non_monograph_leader.jsonl similarity index 100% rename from src/test/resources/samples/marc_non_monograph_leader.jsonl rename to src/test/resources/samples/marc2ld/marc_non_monograph_leader.jsonl diff --git a/src/test/resources/samples/marc2ld/small_instance.jsonl b/src/test/resources/samples/marc2ld/small_instance.jsonl new file mode 100644 index 00000000..401e6e3b --- /dev/null +++ b/src/test/resources/samples/marc2ld/small_instance.jsonl @@ -0,0 +1,59 @@ +{ + "leader": "01767cam a22003977i 4500", + "fields": [ + { + "245": { + "subfields": [ + { + "a": "Instance MainTitle" + }, + { + "n": "8" + }, + { + "p": "Instance PartName" + }, + { + "b": "Instance SubTitle" + }, + { + "c": "Statement Of Responsibility" + } + ], + "ind1": "1", + "ind2": "7" + } + }, + { + "264": { + "subfields": [ + { + "a": "Publication Place" + }, + { + "b": "Publication Name" + }, + { + "c": "2024" + } + ], + "ind1": " ", + "ind2": "1" + } + }, + { + "999": { + "subfields": [ + { + "i": "2165ef4b-001f-46b3-a60e-52bcdeb3d5a1" + }, + { + "s": "43d58061-decf-4d74-9747-0e1c368e861b" + } + ], + "ind1": "f", + "ind2": "f" + } + } + ] +} diff --git a/src/test/resources/samples/marc2ld/small_instance_upd.jsonl b/src/test/resources/samples/marc2ld/small_instance_upd.jsonl new file mode 100644 index 00000000..45c5e1e6 --- /dev/null +++ b/src/test/resources/samples/marc2ld/small_instance_upd.jsonl @@ -0,0 +1,39 @@ +{ + "leader": "01767cam a22003977i 4500", + "fields": [ + { + "245": { + "subfields": [ + { + "a": "Instance MainTitle" + }, + { + "b": "Instance SubTitle UPDATED" + }, + { + "c": "Statement Of Responsibility UPDATED" + } + ], + "ind1": "1", + "ind2": "7" + } + }, + { + "264": { + "subfields": [ + { + "a": "Publication Place NEW" + }, + { + "b": "Publication Name NEW" + }, + { + "c": "2000" + } + ], + "ind1": " ", + "ind2": "1" + } + } + ] +}