Skip to content

Commit

Permalink
Merge branch 'master' into MODLD-582
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiBordak authored Nov 25, 2024
2 parents fd2d103 + 2fbcc74 commit 1b384c1
Show file tree
Hide file tree
Showing 28 changed files with 445 additions and 177 deletions.
2 changes: 1 addition & 1 deletion .run/LinkedDataApplication [Local-Standalone].run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
15 changes: 12 additions & 3 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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"
]
},
{
Expand Down Expand Up @@ -67,7 +69,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"
]
},
{
Expand Down Expand Up @@ -178,6 +182,10 @@
{
"id": "search",
"version": "1.3"
},
{
"id": "specification-storage",
"version": "1.0"
}
],
"permissionSets": [
Expand Down Expand Up @@ -283,7 +291,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" }
]
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<lib-linked-data-marc4ld.version>1.0.2-SNAPSHOT</lib-linked-data-marc4ld.version>
<lib-linked-data-fingerprint.version>1.0.1-SNAPSHOT</lib-linked-data-fingerprint.version>
<mod-source-record-storage-client.version>5.9.1</mod-source-record-storage-client.version>
<mod-record-specifications-dto.version>1.1.0-SNAPSHOT</mod-record-specifications-dto.version>
<maven-checkstyle-plugin.checkstyle.version>10.20.1</maven-checkstyle-plugin.checkstyle.version>

<!-- Test dependencies versions -->
Expand Down Expand Up @@ -102,6 +103,11 @@
<artifactId>mod-source-record-storage-client</artifactId>
<version>${mod-source-record-storage-client.version}</version>
</dependency>
<dependency>
<groupId>org.folio</groupId>
<artifactId>mod-record-specifications-dto</artifactId>
<version>${mod-record-specifications-dto.version}</version>
</dependency>
<dependency>
<groupId>org.folio</groupId>
<artifactId>folio-kafka-wrapper</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*"}))
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/folio/linked/data/client/SpecClient.java
Original file line number Diff line number Diff line change
@@ -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<SpecificationDtoCollection> getBibMarcSpecs();

@Cacheable(cacheNames = SPEC_RULES, key = "#specId")
@GetMapping(value = "/specifications/{specId}/rules")
ResponseEntity<SpecificationRuleDtoCollection> getSpecRules(@PathVariable("specId") UUID specId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TitleFieldRequest> {
public class TitleFieldRequestDeserializer extends JsonDeserializer<TitleFieldRequestTitleInner> {

private static final Map<String, Class<? extends TitleFieldRequest>> IDENDTITY_MAP = Map.of(
private static final Map<String, Class<? extends TitleFieldRequestTitleInner>> IDENDTITY_MAP = Map.of(
TITLE.getUri(), PrimaryTitleField.class,
PARALLEL_TITLE.getUri(), ParallelTitleField.class,
VARIANT_TITLE.getUri(), VariantTitleField.class
);
private final DtoDeserializer<TitleFieldRequest> dtoDeserializer;
private final DtoDeserializer<TitleFieldRequestTitleInner> 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);
}

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,7 +23,7 @@ public Set<Class<?>> supportedParents() {
return SUPPORTED_PARENTS;
}

protected List<String> getPrimaryMainTitles(List<TitleFieldRequest> titles) {
protected List<String> getPrimaryMainTitles(List<TitleFieldRequestTitleInner> titles) {
if (isNull(titles)) {
return new ArrayList<>();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/folio/linked/data/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<LccnPatternConstraint, LccnRequest> {

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);
Expand All @@ -24,6 +32,15 @@ public boolean isValid(LccnRequest lccnRequest, ConstraintValidatorContext const
}
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PrimaryTitleConstraint, List<TitleFieldRequest>> {
public class PrimaryTitleDtoValidator implements
ConstraintValidator<PrimaryTitleConstraint, List<TitleFieldRequestTitleInner>> {

@Override
public boolean isValid(List<TitleFieldRequest> titleFields, ConstraintValidatorContext context) {
public boolean isValid(List<TitleFieldRequestTitleInner> titleFields, ConstraintValidatorContext context) {
if (isNull(titleFields)) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SpecificationRuleDto> getSpecRules();
}
Original file line number Diff line number Diff line change
@@ -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<SpecificationRuleDto> 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();
}
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -208,8 +202,7 @@
"$ref": "../common/IdField.json"
}
}
},
"required": ["title"]
}
}
]
}
Loading

0 comments on commit 1b384c1

Please sign in to comment.