From 5936126574878881a811baa06605e817631b487f Mon Sep 17 00:00:00 2001
From: askhat-abishev <150008941+askhat-abishev@users.noreply.github.com>
Date: Mon, 25 Nov 2024 15:04:12 +0500
Subject: [PATCH 1/2] MODLD-599: LCCN validation configuration (#51)
---
...DataApplication [Local-Standalone].run.xml | 2 +-
README.md | 1 +
descriptors/ModuleDescriptor-template.json | 15 ++-
pom.xml | 6 +
.../linked/data/LinkedDataApplication.java | 2 +
.../folio/linked/data/client/SpecClient.java | 24 ++++
.../linked/data/job/CacheCleaningJob.java | 19 ++++
.../org/folio/linked/data/util/Constants.java | 4 +
.../validation/dto/LccnPatternValidator.java | 19 +++-
.../data/validation/spec/SpecProvider.java | 9 ++
.../spec/impl/SpecProviderImpl.java | 46 ++++++++
src/main/resources/application.yml | 3 +
.../resource/ResourceControllerITBase.java | 36 ++++++
.../dto/LccnPatternValidatorTest.java | 105 +++++++++++-------
.../spec/impl/SpecProviderImplTest.java | 58 ++++++++++
15 files changed, 305 insertions(+), 44 deletions(-)
create mode 100644 src/main/java/org/folio/linked/data/client/SpecClient.java
create mode 100644 src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
create mode 100644 src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java
create mode 100644 src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java
create mode 100644 src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java
diff --git a/.run/LinkedDataApplication [Local-Standalone].run.xml b/.run/LinkedDataApplication [Local-Standalone].run.xml
index a1699103..b07b7131 100644
--- a/.run/LinkedDataApplication [Local-Standalone].run.xml
+++ b/.run/LinkedDataApplication [Local-Standalone].run.xml
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index cbe5005d..0ec53963 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,7 @@ To run mod-linked-data in standalone mode, set the value of the environment vari
| KAFKA_WORK_SEARCH_INDEX_TOPIC_PARTITIONS`*` | 1 | Custom Work Search Index topic partitions number |
| KAFKA_WORK_SEARCH_INDEX_TOPIC_REPLICATION_FACTOR`*` | - | Custom Work Search Index topic replication factor |
| KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC`*` | inventory.instance_ingress | Custom Inventory Instance Ingress Event topic name |
+| CACHE_TTL_SPEC_RULES_MS | 18000000 | Specifies time to live for `spec-rules` cache |
* Applicable only in FOLIO mode
## REST API
Full list of APIs are documented in [src/main/resources/swagger.api/mod-linked-data.yaml](https://github.com/folio-org/mod-linked-data/blob/master/src/main/resources/swagger.api/mod-linked-data.yaml).
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 283465bf..c0e982d0 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -25,7 +25,9 @@
"source-storage.snapshots.post",
"source-storage.records.formatted.item.get",
"source-storage.records.post",
- "source-storage.records.put"
+ "source-storage.records.put",
+ "specification-storage.specifications.collection.get",
+ "specification-storage.specification.rules.collection.get"
]
},
{
@@ -65,7 +67,9 @@
"source-storage.snapshots.post",
"source-storage.records.formatted.item.get",
"source-storage.records.post",
- "source-storage.records.put"
+ "source-storage.records.put",
+ "specification-storage.specifications.collection.get",
+ "specification-storage.specification.rules.collection.get"
]
},
{
@@ -172,6 +176,10 @@
{
"id": "source-storage-records",
"version": "3.2 3.3"
+ },
+ {
+ "id": "specification-storage",
+ "version": "1.0"
}
],
"permissionSets": [
@@ -277,7 +285,8 @@
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC", "value": "linked-data.work" },
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC_PARTITIONS", "value": "1" },
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC_REPLICATION_FACTOR", "value": "" },
- { "name": "KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC", "value": "" }
+ { "name": "KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC", "value": "" },
+ { "name": "CACHE_TTL_SPEC_RULES_MS", "value": "18000000" }
]
}
}
diff --git a/pom.xml b/pom.xml
index 0061791b..9bac90be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
1.0.2-SNAPSHOT
1.0.1-SNAPSHOT
5.9.1
+ 1.1.0-SNAPSHOT
10.20.1
@@ -102,6 +103,11 @@
mod-source-record-storage-client
${mod-source-record-storage-client.version}
+
+ org.folio
+ mod-record-specifications-dto
+ ${mod-record-specifications-dto.version}
+
org.folio
folio-kafka-wrapper
diff --git a/src/main/java/org/folio/linked/data/LinkedDataApplication.java b/src/main/java/org/folio/linked/data/LinkedDataApplication.java
index dc06287a..584494a2 100644
--- a/src/main/java/org/folio/linked/data/LinkedDataApplication.java
+++ b/src/main/java/org/folio/linked/data/LinkedDataApplication.java
@@ -9,10 +9,12 @@
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
@EnableCaching
@EnableAsync
@EnableFeignClients
+@EnableScheduling
@SpringBootApplication
@ComponentScan(value = "org.folio", excludeFilters = @Filter(type = REGEX, pattern =
{"org.folio.spring.tools.systemuser.*", "org.folio.spring.tools.batch.*"}))
diff --git a/src/main/java/org/folio/linked/data/client/SpecClient.java b/src/main/java/org/folio/linked/data/client/SpecClient.java
new file mode 100644
index 00000000..e4170bc5
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/client/SpecClient.java
@@ -0,0 +1,24 @@
+package org.folio.linked.data.client;
+
+import static org.folio.linked.data.util.Constants.Cache.SPEC_RULES;
+
+import java.util.UUID;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@FeignClient(name = "specification-storage")
+public interface SpecClient {
+
+ @Cacheable(cacheNames = "bib-marc-specs")
+ @GetMapping(value = "/specifications?profile=bibliographic&family=MARC")
+ ResponseEntity getBibMarcSpecs();
+
+ @Cacheable(cacheNames = SPEC_RULES, key = "#specId")
+ @GetMapping(value = "/specifications/{specId}/rules")
+ ResponseEntity getSpecRules(@PathVariable("specId") UUID specId);
+}
diff --git a/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java b/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
new file mode 100644
index 00000000..4ed8eb1c
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
@@ -0,0 +1,19 @@
+package org.folio.linked.data.job;
+
+import static org.folio.linked.data.util.Constants.Cache.SPEC_RULES;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class CacheCleaningJob {
+
+ @CacheEvict(value = SPEC_RULES, allEntries = true)
+ @Scheduled(fixedRateString = "${mod-linked-data.cache.ttl.spec-rules}")
+ public void emptySpecRules() {
+ log.info("Emptying {} cache", SPEC_RULES);
+ }
+}
diff --git a/src/main/java/org/folio/linked/data/util/Constants.java b/src/main/java/org/folio/linked/data/util/Constants.java
index 166f8012..56766cf6 100644
--- a/src/main/java/org/folio/linked/data/util/Constants.java
+++ b/src/main/java/org/folio/linked/data/util/Constants.java
@@ -21,4 +21,8 @@ public class Constants {
public static final String MSG_NOT_FOUND_IN = "{} with {}: {} was not found in {}";
public static final String LINKED_DATA_STORAGE = "Linked Data storage";
+ @UtilityClass
+ public static class Cache {
+ public static final String SPEC_RULES = "spec-rules";
+ }
}
diff --git a/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java b/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
index dcb87fc8..e994b1a0 100644
--- a/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
+++ b/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
@@ -5,17 +5,25 @@
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
+import lombok.RequiredArgsConstructor;
import org.folio.linked.data.domain.dto.LccnRequest;
import org.folio.linked.data.validation.LccnPatternConstraint;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+@RequiredArgsConstructor
public class LccnPatternValidator implements ConstraintValidator {
+ public static final String CODE = "invalidLccnSubfieldValue";
+
private static final Pattern LCCN_STRUCTURE_A_PATTERN = Pattern.compile("( {3}|[a-z][ |a-z]{2})\\d{8} ");
private static final Pattern LCCN_STRUCTURE_B_PATTERN = Pattern.compile("( {2}|[a-z][ |a-z])\\d{10}");
+ private final SpecProvider specProvider;
+
@Override
public boolean isValid(LccnRequest lccnRequest, ConstraintValidatorContext constraintValidatorContext) {
- if (isCurrent(lccnRequest)) {
+ if (isCurrent(lccnRequest) && isLccnFormatValidationEnabled()) {
return lccnRequest.getValue()
.stream()
.anyMatch(this::hasValidPattern);
@@ -31,6 +39,15 @@ private boolean isCurrent(LccnRequest lccnRequest) {
.anyMatch(link -> link.endsWith("current"));
}
+ private boolean isLccnFormatValidationEnabled() {
+ return specProvider.getSpecRules()
+ .stream()
+ .filter(rule -> CODE.equals(rule.getCode()))
+ .findFirst()
+ .map(SpecificationRuleDto::getEnabled)
+ .orElse(false);
+ }
+
private boolean hasValidPattern(String lccnValue) {
return LCCN_STRUCTURE_A_PATTERN.matcher(lccnValue).matches()
|| LCCN_STRUCTURE_B_PATTERN.matcher(lccnValue).matches();
diff --git a/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java b/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java
new file mode 100644
index 00000000..04c101d3
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java
@@ -0,0 +1,9 @@
+package org.folio.linked.data.validation.spec;
+
+import java.util.List;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+
+public interface SpecProvider {
+
+ List getSpecRules();
+}
diff --git a/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java b/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java
new file mode 100644
index 00000000..03752689
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java
@@ -0,0 +1,46 @@
+package org.folio.linked.data.validation.spec.impl;
+
+import feign.FeignException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.folio.linked.data.client.SpecClient;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class SpecProviderImpl implements SpecProvider {
+
+ private final SpecClient client;
+
+ @Override
+ public List getSpecRules() {
+ try {
+ return Optional.ofNullable(client.getBibMarcSpecs().getBody())
+ .map(SpecificationDtoCollection::getSpecifications)
+ .stream()
+ .flatMap(Collection::stream)
+ .findFirst()
+ .map(SpecificationDto::getId)
+ .map(client::getSpecRules)
+ .map(ResponseEntity::getBody)
+ .map(SpecificationRuleDtoCollection::getRules)
+ .stream()
+ .flatMap(Collection::stream)
+ .toList();
+ } catch (FeignException e) {
+ log.error("Unexpected exception during specification rules retrieval", e);
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 15a706e0..e612557f 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -79,6 +79,9 @@ mod-linked-data:
topic:
work-search-index: ${KAFKA_WORK_SEARCH_INDEX_TOPIC:linked-data.work}
instance-ingress: ${KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC:inventory.instance_ingress}
+ cache:
+ ttl:
+ spec-rules: ${CACHE_TTL_SPEC_RULES:18000000}
management:
endpoints:
diff --git a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
index 9bed359a..34294b7b 100644
--- a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
+++ b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
@@ -4,6 +4,7 @@
import static java.lang.String.join;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
+import static java.util.UUID.randomUUID;
import static java.util.stream.StreamSupport.stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.ld.dictionary.PredicateDictionary.ACCESS_LOCATION;
@@ -154,6 +155,7 @@
import org.folio.ld.dictionary.PredicateDictionary;
import org.folio.ld.dictionary.PropertyDictionary;
import org.folio.ld.dictionary.ResourceTypeDictionary;
+import org.folio.linked.data.client.SpecClient;
import org.folio.linked.data.client.SrsClient;
import org.folio.linked.data.domain.dto.InstanceResponseField;
import org.folio.linked.data.domain.dto.ResourceIndexEventType;
@@ -167,8 +169,13 @@
import org.folio.linked.data.service.resource.hash.HashService;
import org.folio.linked.data.test.ResourceTestService;
import org.folio.linked.data.test.TestUtil;
+import org.folio.linked.data.validation.dto.LccnPatternValidator;
import org.folio.rest.jaxrs.model.ParsedRecord;
import org.folio.rest.jaxrs.model.Record;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -214,6 +221,8 @@ public abstract class ResourceControllerITBase {
private HashService hashService;
@MockBean
private SrsClient srsClient;
+ @MockBean
+ private SpecClient specClient;
@BeforeEach
public void beforeEach() {
@@ -256,6 +265,10 @@ void createInstanceWithWorkRef_shouldSaveEntityCorrectly() throws Exception {
@Test
void createInstanceWithWorkRef_shouldReturn400_ifLccnIsInvalid() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var work = getSampleWork(null);
setExistingResourcesIds(work);
resourceTestService.saveGraph(work);
@@ -376,6 +389,10 @@ void createWorkWithInstanceRef_shouldSaveEntityCorrectly() throws Exception {
@Test
void update_shouldReturnCorrectlyUpdatedInstanceWithWorkRef_deleteOldOne_sendMessages() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var work = getSampleWork(null);
var originalInstance = resourceTestService.saveGraph(getSampleInstanceResource(null, work));
var updateDto = getSampleInstanceDtoMap();
@@ -430,6 +447,10 @@ void update_shouldReturnCorrectlyUpdatedInstanceWithWorkRef_deleteOldOne_sendMes
@Test
void update_shouldReturn400_ifLccnIsInvalid() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var updateDto = getSampleInstanceDtoMap();
var instance = (LinkedHashMap) ((LinkedHashMap) updateDto.get("resource")).get(INSTANCE.getUri());
instance.remove("inventoryId");
@@ -2064,6 +2085,21 @@ private ArrayList getStatus(LinkedHashMap instance) {
return (ArrayList) lccn.get(STATUS.getUri());
}
+ private SpecificationDtoCollection createSpecifications(UUID specRuleId) {
+ var specifications = new SpecificationDtoCollection();
+ specifications.setSpecifications(List.of(new SpecificationDto().id(specRuleId)));
+ return specifications;
+ }
+
+ private SpecificationRuleDtoCollection createSpecRules() {
+ var specRules = new SpecificationRuleDtoCollection();
+ var specRule = new SpecificationRuleDto();
+ specRule.setCode(LccnPatternValidator.CODE);
+ specRule.setEnabled(true);
+ specRules.setRules(List.of(specRule));
+ return specRules;
+ }
+
private record LookupResources(
List subjects,
List geographicCoverages,
diff --git a/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java b/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
index 66f03b6e..17714e7c 100644
--- a/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
+++ b/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
@@ -4,85 +4,105 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.doReturn;
import java.util.List;
import java.util.stream.Stream;
import org.folio.linked.data.domain.dto.LccnRequest;
import org.folio.linked.data.domain.dto.Status;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
import org.folio.spring.testing.type.UnitTest;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
@UnitTest
+@ExtendWith(MockitoExtension.class)
class LccnPatternValidatorTest {
- private final LccnPatternValidator validator = new LccnPatternValidator();
+ @Mock
+ private SpecProvider specProvider;
+
+ @InjectMocks
+ private LccnPatternValidator validator;
@ParameterizedTest
- @MethodSource("validLccnProvider")
- void shouldReturnTrue_ifLccnIsValid(String value, String link) {
+ @MethodSource("positiveCaseProvider")
+ void shouldReturnTrue(String value, String link, List specRules) {
// given
var lccnRequest = createLccnRequest(value, link);
+ if (!"http://id.loc.gov/vocabulary/mstatus/cancinv".equals(link)) {
+ doReturn(specRules).when(specProvider).getSpecRules();
+ }
// expect
assertTrue(validator.isValid(lccnRequest, null));
}
- private static Stream validLccnProvider() {
+ private static Stream positiveCaseProvider() {
return Stream.of(
// structure A
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn12345678 ", null),
- arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/cancinv"),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn12345678 ", null, createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/cancinv", createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(false)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", List.of()),
// structure B
- arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("n 0123456781", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn0123456789", null),
- arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/cancinv"));
+ arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("n 0123456781", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn0123456789", null, createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/cancinv", createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(false)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", List.of()));
}
@ParameterizedTest
- @MethodSource("invalidLccnProvider")
- void shouldReturnFalse_ifLccnIsInvalid(String value, String link) {
+ @MethodSource("negativeCaseProvider")
+ void shouldReturnFalse(String value, String link, List specRules) {
// given
var lccnRequest = createLccnRequest(value, link);
+ doReturn(specRules).when(specProvider).getSpecRules();
// expect
assertFalse(validator.isValid(lccnRequest, null));
}
- public static Stream invalidLccnProvider() {
+ public static Stream negativeCaseProvider() {
return Stream.of(
// structure A
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 1234567 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 12345678", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nNn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nNn12345678 ", null),
- arguments("n-n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 1234567 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 12345678", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nNn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nNn12345678 ", null, createSpecRules(true)),
+ arguments("n-n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
// structure B
- arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" m123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" m123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nN0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nN0123456789", null),
- arguments("n-0123456789", "http://id.loc.gov/vocabulary/mstatus/current"));
+ arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" m123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" m123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nN0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nN0123456789", null, createSpecRules(true)),
+ arguments("n-0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)));
}
private LccnRequest createLccnRequest(String value, String link) {
@@ -90,4 +110,11 @@ private LccnRequest createLccnRequest(String value, String link) {
.value(List.of(value))
.status(link == null ? emptyList() : List.of(new Status().link(List.of(link))));
}
+
+ private static List createSpecRules(boolean enabled) {
+ var specRule = new SpecificationRuleDto();
+ specRule.setCode(LccnPatternValidator.CODE);
+ specRule.setEnabled(enabled);
+ return List.of(specRule);
+ }
}
diff --git a/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java b/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java
new file mode 100644
index 00000000..72140a69
--- /dev/null
+++ b/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java
@@ -0,0 +1,58 @@
+package org.folio.linked.data.validation.spec.impl;
+
+import static java.util.Collections.emptyList;
+import static java.util.UUID.randomUUID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import feign.FeignException;
+import java.util.List;
+import org.folio.linked.data.client.SpecClient;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+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.http.ResponseEntity;
+
+@UnitTest
+@ExtendWith(MockitoExtension.class)
+class SpecProviderImplTest {
+
+ @Mock
+ private SpecClient client;
+
+ @InjectMocks
+ private SpecProviderImpl specProvider;
+
+ @Test
+ void shouldReturn_specRules_whenSpecificationStorageIsAvailable() {
+ //given
+ var specRuleId = randomUUID();
+ var specifications = new SpecificationDtoCollection();
+ var specRule = new SpecificationRuleDto();
+ var specRules = new SpecificationRuleDtoCollection();
+ specRules.setRules(List.of(specRule));
+ specifications.setSpecifications(List.of(new SpecificationDto().id(specRuleId)));
+ doReturn(ResponseEntity.ok().body(specifications)).when(client).getBibMarcSpecs();
+ doReturn(ResponseEntity.ok().body(specRules)).when(client).getSpecRules(specRuleId);
+
+ //expect
+ assertEquals(List.of(specRule), specProvider.getSpecRules());
+ }
+
+ @Test
+ void shouldReturn_emptyList_whenSpecificationStorageIsNotAvailable() {
+ //given
+ when(client.getBibMarcSpecs()).thenThrow(FeignException.class);
+
+ //expect
+ assertEquals(emptyList(), specProvider.getSpecRules());
+ }
+}
From 2fbcc7402028ee9bac957bf952368ffb8a4892bb Mon Sep 17 00:00:00 2001
From: pkjacob <121239864+pkjacob@users.noreply.github.com>
Date: Mon, 25 Nov 2024 08:02:55 -0500
Subject: [PATCH 2/2] MODLD-602: Updated TitleFieldRequest & TitleFieldResponse
to avoid oneOf at the top level (#57)
* MODLD-602: Updated TitleFieldResponse to avoid oneOf at the top level
* MODLD-602: Updated TitleFieldRequest to avoid oneOf at the top level
---
.../json/ObjectMapperConfig.java | 4 +-
.../title/TitleFieldRequestDeserializer.java | 12 +--
.../dto/monograph/TopResourceMapperUnit.java | 4 +-
.../dto/PrimaryTitleDtoValidator.java | 7 +-
.../resource/request/InstanceRequest.json | 15 +---
.../schema/resource/request/WorkRequest.json | 15 +---
.../request/title/TitleFieldRequest.json | 86 +++++++++++--------
.../resource/response/InstanceResponse.json | 11 +--
.../resource/response/WorkResponse.json | 11 +--
.../response/title/TitleFieldResponse.json | 82 ++++++++++--------
.../org/folio/linked/data/test/TestUtil.java | 4 +-
.../json/TitleFieldResponseDeserializer.java | 12 +--
.../dto/PrimaryTitleDtoValidatorTest.java | 10 +--
13 files changed, 140 insertions(+), 133 deletions(-)
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 f5a75c5f..42203507 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
@@ -14,7 +14,7 @@
import org.folio.linked.data.domain.dto.MarcRecord;
import org.folio.linked.data.domain.dto.ResourceRequestField;
import org.folio.linked.data.domain.dto.SourceRecordDomainEvent;
-import org.folio.linked.data.domain.dto.TitleFieldRequest;
+import org.folio.linked.data.domain.dto.TitleFieldRequestTitleInner;
import org.folio.linked.data.exception.RequestProcessingExceptionBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -38,7 +38,7 @@ public ObjectMapper objectMapper(RequestProcessingExceptionBuilder exceptionBuil
private Module monographModule(ObjectMapper objectMapper, RequestProcessingExceptionBuilder exceptionBuilder) {
return new SimpleModule()
.addDeserializer(ResourceRequestField.class, new ResourceRequestFieldDeserializer(exceptionBuilder))
- .addDeserializer(TitleFieldRequest.class, new TitleFieldRequestDeserializer(exceptionBuilder))
+ .addDeserializer(TitleFieldRequestTitleInner.class, new TitleFieldRequestDeserializer(exceptionBuilder))
.addDeserializer(InstanceRequestAllOfMap.class, new InstanceRequestAllOfMapDeserializer(exceptionBuilder))
.addDeserializer(SourceRecordDomainEvent.class, new SourceRecordDomainEventDeserializer(objectMapper));
}
diff --git a/src/main/java/org/folio/linked/data/configuration/json/deserialization/title/TitleFieldRequestDeserializer.java b/src/main/java/org/folio/linked/data/configuration/json/deserialization/title/TitleFieldRequestDeserializer.java
index d1ccd735..f6e94126 100644
--- a/src/main/java/org/folio/linked/data/configuration/json/deserialization/title/TitleFieldRequestDeserializer.java
+++ b/src/main/java/org/folio/linked/data/configuration/json/deserialization/title/TitleFieldRequestDeserializer.java
@@ -11,26 +11,26 @@
import java.util.Map;
import org.folio.linked.data.domain.dto.ParallelTitleField;
import org.folio.linked.data.domain.dto.PrimaryTitleField;
-import org.folio.linked.data.domain.dto.TitleFieldRequest;
+import org.folio.linked.data.domain.dto.TitleFieldRequestTitleInner;
import org.folio.linked.data.domain.dto.VariantTitleField;
import org.folio.linked.data.exception.RequestProcessingExceptionBuilder;
import org.folio.linked.data.util.DtoDeserializer;
-public class TitleFieldRequestDeserializer extends JsonDeserializer {
+public class TitleFieldRequestDeserializer extends JsonDeserializer {
- private static final Map> IDENDTITY_MAP = Map.of(
+ private static final Map> IDENDTITY_MAP = Map.of(
TITLE.getUri(), PrimaryTitleField.class,
PARALLEL_TITLE.getUri(), ParallelTitleField.class,
VARIANT_TITLE.getUri(), VariantTitleField.class
);
- private final DtoDeserializer dtoDeserializer;
+ private final DtoDeserializer dtoDeserializer;
public TitleFieldRequestDeserializer(RequestProcessingExceptionBuilder exceptionBuilder) {
- dtoDeserializer = new DtoDeserializer<>(TitleFieldRequest.class, IDENDTITY_MAP, exceptionBuilder);
+ dtoDeserializer = new DtoDeserializer<>(TitleFieldRequestTitleInner.class, IDENDTITY_MAP, exceptionBuilder);
}
@Override
- public TitleFieldRequest deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
+ public TitleFieldRequestTitleInner deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
return dtoDeserializer.deserialize(jp);
}
diff --git a/src/main/java/org/folio/linked/data/mapper/dto/monograph/TopResourceMapperUnit.java b/src/main/java/org/folio/linked/data/mapper/dto/monograph/TopResourceMapperUnit.java
index 578fdd11..67d63d82 100644
--- a/src/main/java/org/folio/linked/data/mapper/dto/monograph/TopResourceMapperUnit.java
+++ b/src/main/java/org/folio/linked/data/mapper/dto/monograph/TopResourceMapperUnit.java
@@ -8,7 +8,7 @@
import org.folio.linked.data.domain.dto.PrimaryTitleField;
import org.folio.linked.data.domain.dto.ResourceRequestDto;
import org.folio.linked.data.domain.dto.ResourceResponseDto;
-import org.folio.linked.data.domain.dto.TitleFieldRequest;
+import org.folio.linked.data.domain.dto.TitleFieldRequestTitleInner;
import org.folio.linked.data.mapper.dto.common.SingleResourceMapperUnit;
public abstract class TopResourceMapperUnit implements SingleResourceMapperUnit {
@@ -23,7 +23,7 @@ public Set> supportedParents() {
return SUPPORTED_PARENTS;
}
- protected List getPrimaryMainTitles(List titles) {
+ protected List getPrimaryMainTitles(List titles) {
if (isNull(titles)) {
return new ArrayList<>();
}
diff --git a/src/main/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidator.java b/src/main/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidator.java
index ba83e830..bae3e6ab 100644
--- a/src/main/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidator.java
+++ b/src/main/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidator.java
@@ -8,13 +8,14 @@
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.folio.linked.data.domain.dto.PrimaryTitleField;
-import org.folio.linked.data.domain.dto.TitleFieldRequest;
+import org.folio.linked.data.domain.dto.TitleFieldRequestTitleInner;
import org.folio.linked.data.validation.PrimaryTitleConstraint;
-public class PrimaryTitleDtoValidator implements ConstraintValidator> {
+public class PrimaryTitleDtoValidator implements
+ ConstraintValidator> {
@Override
- public boolean isValid(List titleFields, ConstraintValidatorContext context) {
+ public boolean isValid(List titleFields, ConstraintValidatorContext context) {
if (isNull(titleFields)) {
return true;
}
diff --git a/src/main/resources/swagger.api/schema/resource/request/InstanceRequest.json b/src/main/resources/swagger.api/schema/resource/request/InstanceRequest.json
index 4c699ae0..befc9bae 100644
--- a/src/main/resources/swagger.api/schema/resource/request/InstanceRequest.json
+++ b/src/main/resources/swagger.api/schema/resource/request/InstanceRequest.json
@@ -2,18 +2,12 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Instance request DTO",
"allOf": [
+ {
+ "$ref": "title/TitleFieldRequest.json"
+ },
{
"type": "object",
"properties": {
- "title": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "title/TitleFieldRequest.json"
- },
- "x-json-property": "http://bibfra.me/vocab/marc/title",
- "x-field-extra-annotation": "@org.folio.linked.data.validation.PrimaryTitleConstraint"
- },
"production": {
"type": "array",
"items": {
@@ -208,8 +202,7 @@
"$ref": "../common/IdField.json"
}
}
- },
- "required": ["title"]
+ }
}
]
}
diff --git a/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json b/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json
index c5842bb6..09abfb15 100644
--- a/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json
+++ b/src/main/resources/swagger.api/schema/resource/request/WorkRequest.json
@@ -2,18 +2,12 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Work request DTO",
"allOf": [
+ {
+ "$ref": "title/TitleFieldRequest.json"
+ },
{
"type": "object",
"properties": {
- "title": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "title/TitleFieldRequest.json"
- },
- "x-json-property": "http://bibfra.me/vocab/marc/title",
- "x-field-extra-annotation": "@org.folio.linked.data.validation.PrimaryTitleConstraint"
- },
"targetAudience": {
"type": "array",
"items": {
@@ -149,8 +143,7 @@
},
"x-json-property": "http://bibfra.me/vocab/scholar/dissertation"
}
- },
- "required": ["title"]
+ }
}
]
}
diff --git a/src/main/resources/swagger.api/schema/resource/request/title/TitleFieldRequest.json b/src/main/resources/swagger.api/schema/resource/request/title/TitleFieldRequest.json
index 3a8c29ab..5b44ddd7 100644
--- a/src/main/resources/swagger.api/schema/resource/request/title/TitleFieldRequest.json
+++ b/src/main/resources/swagger.api/schema/resource/request/title/TitleFieldRequest.json
@@ -1,38 +1,54 @@
{
- "description": "A title of a resource.",
- "oneOf": [
- {
- "type": "object",
- "title": "PrimaryTitleField",
- "properties": {
- "PrimaryTitle": {
- "type": "object",
- "$ref": "schema/resource/common/title/PrimaryTitle.json",
- "x-json-property": "http://bibfra.me/vocab/marc/Title"
- }
- }
- },
- {
- "type": "object",
- "title": "VariantTitleField",
- "properties": {
- "VariantTitle": {
- "type": "object",
- "$ref": "schema/resource/common/title/VariantTitle.json",
- "x-json-property": "http://bibfra.me/vocab/marc/VariantTitle"
- }
- }
- },
- {
- "type": "object",
- "title": "ParallelTitleField",
- "properties": {
- "ParallelTitle": {
- "type": "object",
- "$ref": "schema/resource/common/title/ParallelTitle.json",
- "x-json-property": "http://bibfra.me/vocab/marc/ParallelTitle"
- }
- }
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Title of the Work or Instance resource",
+ "properties": {
+ "title": {
+ "type": "array",
+ "description": "The title of the work or instance",
+ "items": {
+ "type": "object",
+ "oneOf": [
+ {
+ "type": "object",
+ "title": "PrimaryTitleField",
+ "properties": {
+ "PrimaryTitle": {
+ "type": "object",
+ "$ref": "../../common/title/PrimaryTitle.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/Title",
+ "description": "The primary title of the work or instance"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "VariantTitleField",
+ "properties": {
+ "VariantTitle": {
+ "type": "object",
+ "$ref": "../../common/title/VariantTitle.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/VariantTitle",
+ "description": "The variant title of the work or instance"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "ParallelTitleField",
+ "properties": {
+ "ParallelTitle": {
+ "type": "object",
+ "$ref": "../../common/title/ParallelTitle.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/ParallelTitle",
+ "description": "The parallel title of the work or instance"
+ }
+ }
+ }
+ ]
+ },
+ "x-json-property": "http://bibfra.me/vocab/marc/title",
+ "x-field-extra-annotation": "@org.folio.linked.data.validation.PrimaryTitleConstraint"
}
- ]
+ },
+ "required": ["title"]
}
diff --git a/src/main/resources/swagger.api/schema/resource/response/InstanceResponse.json b/src/main/resources/swagger.api/schema/resource/response/InstanceResponse.json
index 8b5a7232..b66cf920 100644
--- a/src/main/resources/swagger.api/schema/resource/response/InstanceResponse.json
+++ b/src/main/resources/swagger.api/schema/resource/response/InstanceResponse.json
@@ -5,17 +5,12 @@
{
"$ref": "../common/IdField.json"
},
+ {
+ "$ref": "title/TitleFieldResponse.json"
+ },
{
"type": "object",
"properties": {
- "title": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "title/TitleFieldResponse.json"
- },
- "x-json-property": "http://bibfra.me/vocab/marc/title"
- },
"production": {
"type": "array",
"items": {
diff --git a/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json b/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json
index 737d8d3d..dcb71034 100644
--- a/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json
+++ b/src/main/resources/swagger.api/schema/resource/response/WorkResponse.json
@@ -5,17 +5,12 @@
{
"$ref": "../common/IdField.json"
},
+ {
+ "$ref": "title/TitleFieldResponse.json"
+ },
{
"type": "object",
"properties": {
- "title": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "title/TitleFieldResponse.json"
- },
- "x-json-property": "http://bibfra.me/vocab/marc/title"
- },
"targetAudience": {
"type": "array",
"items": {
diff --git a/src/main/resources/swagger.api/schema/resource/response/title/TitleFieldResponse.json b/src/main/resources/swagger.api/schema/resource/response/title/TitleFieldResponse.json
index 02feca37..2f072de3 100644
--- a/src/main/resources/swagger.api/schema/resource/response/title/TitleFieldResponse.json
+++ b/src/main/resources/swagger.api/schema/resource/response/title/TitleFieldResponse.json
@@ -1,38 +1,52 @@
{
+ "$schema": "http://json-schema.org/draft-04/schema#",
"description": "Title of the Work or Instance resource",
- "oneOf": [
- {
- "type": "object",
- "title": "PrimaryTitleFieldResponse",
- "properties": {
- "PrimaryTitle": {
- "type": "object",
- "$ref": "schema/resource/response/title/PrimaryTitleResponse.json",
- "x-json-property": "http://bibfra.me/vocab/marc/Title"
- }
- }
- },
- {
- "type": "object",
- "title": "VariantTitleFieldResponse",
- "properties": {
- "VariantTitle": {
- "type": "object",
- "$ref": "schema/resource/response/title/VariantTitleResponse.json",
- "x-json-property": "http://bibfra.me/vocab/marc/VariantTitle"
- }
- }
- },
- {
- "type": "object",
- "title": "ParallelTitleFieldResponse",
- "properties": {
- "ParallelTitle": {
- "type": "object",
- "$ref": "schema/resource/response/title/ParallelTitleResponse.json",
- "x-json-property": "http://bibfra.me/vocab/marc/ParallelTitle"
- }
- }
+ "properties": {
+ "title": {
+ "type": "array",
+ "description": "The title of the work or instance",
+ "items": {
+ "type": "object",
+ "oneOf": [
+ {
+ "type": "object",
+ "title": "PrimaryTitleFieldResponse",
+ "properties": {
+ "PrimaryTitle": {
+ "type": "object",
+ "$ref": "PrimaryTitleResponse.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/Title",
+ "description": "The primary title of the work or instance"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "VariantTitleFieldResponse",
+ "properties": {
+ "VariantTitle": {
+ "type": "object",
+ "$ref": "VariantTitleResponse.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/VariantTitle",
+ "description": "The variant title of the work or instance"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "ParallelTitleFieldResponse",
+ "properties": {
+ "ParallelTitle": {
+ "type": "object",
+ "$ref": "ParallelTitleResponse.json",
+ "x-json-property": "http://bibfra.me/vocab/marc/ParallelTitle",
+ "description": "The parallel title of the work or instance"
+ }
+ }
+ }
+ ]
+ },
+ "x-json-property": "http://bibfra.me/vocab/marc/title"
}
- ]
+ }
}
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 4b18e890..50b9d4b2 100644
--- a/src/test/java/org/folio/linked/data/test/TestUtil.java
+++ b/src/test/java/org/folio/linked/data/test/TestUtil.java
@@ -37,7 +37,7 @@
import org.folio.linked.data.configuration.json.ObjectMapperConfig;
import org.folio.linked.data.domain.dto.InstanceResponseAllOfMap;
import org.folio.linked.data.domain.dto.ResourceResponseField;
-import org.folio.linked.data.domain.dto.TitleFieldResponse;
+import org.folio.linked.data.domain.dto.TitleFieldResponseTitleInner;
import org.folio.linked.data.exception.RequestProcessingExceptionBuilder;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.test.json.InstanceResponseAllOfMapDeserializer;
@@ -72,7 +72,7 @@ public class TestUtil {
static {
OBJECT_MAPPER.registerModule(new SimpleModule()
.addDeserializer(ResourceResponseField.class, new ResourceResponseFieldDeserializer())
- .addDeserializer(TitleFieldResponse.class, new TitleFieldResponseDeserializer())
+ .addDeserializer(TitleFieldResponseTitleInner.class, new TitleFieldResponseDeserializer())
.addDeserializer(InstanceResponseAllOfMap.class, new InstanceResponseAllOfMapDeserializer())
);
PARAMETERS.excludeField(named("id"));
diff --git a/src/test/java/org/folio/linked/data/test/json/TitleFieldResponseDeserializer.java b/src/test/java/org/folio/linked/data/test/json/TitleFieldResponseDeserializer.java
index 7c3e5531..2e19fc15 100644
--- a/src/test/java/org/folio/linked/data/test/json/TitleFieldResponseDeserializer.java
+++ b/src/test/java/org/folio/linked/data/test/json/TitleFieldResponseDeserializer.java
@@ -12,21 +12,21 @@
import java.util.Map;
import org.folio.linked.data.domain.dto.ParallelTitleFieldResponse;
import org.folio.linked.data.domain.dto.PrimaryTitleFieldResponse;
-import org.folio.linked.data.domain.dto.TitleFieldResponse;
+import org.folio.linked.data.domain.dto.TitleFieldResponseTitleInner;
import org.folio.linked.data.domain.dto.VariantTitleFieldResponse;
import org.folio.linked.data.util.DtoDeserializer;
-public class TitleFieldResponseDeserializer extends JsonDeserializer {
- private static final Map> IDENDTITY_MAP = Map.of(
+public class TitleFieldResponseDeserializer extends JsonDeserializer {
+ private static final Map> IDENDTITY_MAP = Map.of(
TITLE.getUri(), PrimaryTitleFieldResponse.class,
PARALLEL_TITLE.getUri(), ParallelTitleFieldResponse.class,
VARIANT_TITLE.getUri(), VariantTitleFieldResponse.class
);
- private final DtoDeserializer dtoDeserializer =
- new DtoDeserializer<>(TitleFieldResponse.class, IDENDTITY_MAP, EMPTY_EXCEPTION_BUILDER);
+ private final DtoDeserializer dtoDeserializer =
+ new DtoDeserializer<>(TitleFieldResponseTitleInner.class, IDENDTITY_MAP, EMPTY_EXCEPTION_BUILDER);
@Override
- public TitleFieldResponse deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
+ public TitleFieldResponseTitleInner deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
return dtoDeserializer.deserialize(jp);
}
}
diff --git a/src/test/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidatorTest.java b/src/test/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidatorTest.java
index f629da1a..7d1eb7f3 100644
--- a/src/test/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidatorTest.java
+++ b/src/test/java/org/folio/linked/data/validation/dto/PrimaryTitleDtoValidatorTest.java
@@ -8,7 +8,7 @@
import org.folio.linked.data.domain.dto.ParallelTitleField;
import org.folio.linked.data.domain.dto.PrimaryTitle;
import org.folio.linked.data.domain.dto.PrimaryTitleField;
-import org.folio.linked.data.domain.dto.TitleFieldRequest;
+import org.folio.linked.data.domain.dto.TitleFieldRequestTitleInner;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.Test;
@@ -29,7 +29,7 @@ void shouldReturnTrue_ifGivenTitleFieldRequestListIsNull() {
@Test
void shouldReturnFalse_ifGivenTitleFieldRequestListIsEmpty() {
// given
- var titleFields = new ArrayList();
+ var titleFields = new ArrayList();
// when
boolean result = validator.isValid(titleFields, null);
@@ -41,7 +41,7 @@ void shouldReturnFalse_ifGivenTitleFieldRequestListIsEmpty() {
@Test
void shouldReturnFalse_ifGivenTitleFieldRequestListContainsNoPrimaryTitle() {
// given
- var titleFields = new ArrayList();
+ var titleFields = new ArrayList();
titleFields.add(new ParallelTitleField().parallelTitle(
new ParallelTitle().mainTitle(List.of("parallel main title"))
));
@@ -56,7 +56,7 @@ void shouldReturnFalse_ifGivenTitleFieldRequestListContainsNoPrimaryTitle() {
@Test
void shouldReturnFalse_ifGivenTitleFieldRequestListContainsPrimaryTitleWithNoMainTitle() {
// given
- var titleFields = new ArrayList();
+ var titleFields = new ArrayList();
titleFields.add(new ParallelTitleField().parallelTitle(
new ParallelTitle().mainTitle(List.of("parallel main title"))
));
@@ -74,7 +74,7 @@ void shouldReturnFalse_ifGivenTitleFieldRequestListContainsPrimaryTitleWithNoMai
@Test
void shouldReturnTrue_ifGivenTitleFieldListContainsPrimaryTitleWithMainTitle() {
// given
- var titleFields = new ArrayList();
+ var titleFields = new ArrayList();
titleFields.add(new ParallelTitleField().parallelTitle(
new ParallelTitle().mainTitle(List.of("parallel main title"))
));