Skip to content

Commit

Permalink
MODTLR-10: Support for cross-tenant requests (#10)
Browse files Browse the repository at this point in the history
* MODTLR-10 Basic implementation and tests

* MODTLR-10 Add logging configuration file

* MODTLR-10 Add requestDate to ECS TLR schema, fix test

* MODTLR-10 Replace dummy tenantId with "university"

* MODTLR-10 Revert cosmetic changes

* MODTLR-10 Attempt to fix Kafka listener test: increase timeout

* MODTLR-10 Fix code smells

* MODTLR-10 Test for TenantScopedExecutionService

* MODTLR-10 Remove unused imports

* MODTLR-10 Add Kafka environment variables

* MODTLR-10 Add env variable OKAPI_URL

* MODTLR-10 Extend EcsTlrApiTest

* MODTLR-10 Minor adjustments

* MODTLR-10 Rename method

* MODTLR-10 Replace "=" with ":" in logs

* MODTLR-10 Make requestDate required
  • Loading branch information
OleksandrVidinieiev authored Jan 26, 2024
1 parent d0cb1c0 commit 6b4093b
Show file tree
Hide file tree
Showing 25 changed files with 849 additions and 16 deletions.
19 changes: 17 additions & 2 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"methods": ["POST"],
"pathPattern": "/tlr/ecs-tlr",
"permissionsRequired": ["tlr.ecs-tlr.post"],
"modulePermissions": []
"modulePermissions": [
"circulation.requests.item.post"
]
}
]
},
Expand Down Expand Up @@ -71,9 +73,22 @@
}
},
"env": [
{ "name": "JAVA_OPTIONS",
{
"name": "JAVA_OPTIONS",
"value": "-XX:MaxRAMPercentage=66.0"
},
{
"name": "OKAPI_URL",
"value": "http://okapi:9130"
},
{
"name": "KAFKA_HOST",
"value": "kafka"
},
{
"name": "KAFKA_PORT",
"value": "9092"
},
{ "name": "DB_HOST", "value": "postgres" },
{ "name": "DB_PORT", "value": "5432" },
{ "name": "DB_USERNAME", "value": "folio_admin" },
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/folio/client/CirculationClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.folio.client;

import org.folio.domain.dto.Request;
import org.folio.spring.config.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;

@FeignClient(name = "circulation", url = "${folio.okapi-url}", configuration = FeignClientConfiguration.class)
public interface CirculationClient {

@PostMapping("/circulation/requests")
Request createRequest(Request request);
}
1 change: 1 addition & 0 deletions src/main/java/org/folio/domain/entity/EcsTlrEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class EcsTlrEntity {
private String requestType;
private String requestLevel;
private Date requestExpirationDate;
private Date requestDate;
private String patronComments;
private String fulfillmentPreference;
private UUID pickupServicePointId;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/folio/domain/mapper/EcsTlrMapper.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.folio.domain.mapper;

import org.folio.domain.dto.EcsTlr;
import org.folio.domain.dto.Request;
import org.folio.domain.entity.EcsTlrEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
Expand Down Expand Up @@ -49,4 +50,6 @@ default String mapRequestLevelToString(EcsTlr.RequestLevelEnum requestLevelEnum)
default String mapFulfillmentPreferenceToString(EcsTlr.FulfillmentPreferenceEnum fulfillmentPreferenceEnum) {
return fulfillmentPreferenceEnum != null ? fulfillmentPreferenceEnum.getValue() : null;
}

Request mapDtoToRequest(EcsTlr ecsTlr);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.folio.exception;

import lombok.Getter;

@Getter
public class TenantScopedExecutionException extends RuntimeException {
private final String tenantId;

public TenantScopedExecutionException(Exception cause, String tenantId) {
super(cause);
this.tenantId = tenantId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.folio.service;

import java.util.concurrent.Callable;

public interface TenantScopedExecutionService {

<T> T execute(String tenantId, Callable<T> action);
}
17 changes: 17 additions & 0 deletions src/main/java/org/folio/service/impl/EcsTlrServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import java.util.Optional;
import java.util.UUID;

import org.folio.client.CirculationClient;
import org.folio.domain.dto.EcsTlr;
import org.folio.domain.dto.Request;
import org.folio.domain.mapper.EcsTlrMapper;
import org.folio.repository.EcsTlrRepository;
import org.folio.service.TenantScopedExecutionService;
import org.folio.service.EcsTlrService;
import org.springframework.stereotype.Service;

Expand All @@ -19,6 +22,8 @@ public class EcsTlrServiceImpl implements EcsTlrService {

private final EcsTlrRepository ecsTlrRepository;
private final EcsTlrMapper requestsMapper;
private final CirculationClient circulationClient;
private final TenantScopedExecutionService tenantScopedExecutionService;

@Override
public Optional<EcsTlr> get(UUID id) {
Expand All @@ -31,8 +36,20 @@ public Optional<EcsTlr> get(UUID id) {
@Override
public EcsTlr post(EcsTlr ecsTlr) {
log.debug("post:: parameters ecsTlr: {}", () -> ecsTlr);
createRemoteRequest(ecsTlr, "university"); // TODO: replace with real tenantId

return requestsMapper.mapEntityToDto(ecsTlrRepository.save(
requestsMapper.mapDtoToEntity(ecsTlr)));
}

private Request createRemoteRequest(EcsTlr ecsTlr, String tenantId) {
log.info("createRemoteRequest:: creating remote request for ECS TLR {} and tenant {}", ecsTlr.getId(), tenantId);
Request mappedRequest = requestsMapper.mapDtoToRequest(ecsTlr);
Request createdRequest = tenantScopedExecutionService.execute(tenantId,
() -> circulationClient.createRequest(mappedRequest));
log.info("createRemoteRequest:: request created: {}", createdRequest.getId());
log.debug("createRemoteRequest:: request: {}", () -> createdRequest);

return createdRequest;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.folio.service.impl;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.folio.exception.TenantScopedExecutionException;
import org.folio.service.TenantScopedExecutionService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.FolioModuleMetadata;
import org.folio.spring.integration.XOkapiHeaders;
import org.folio.spring.scope.FolioExecutionContextSetter;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Service
@RequiredArgsConstructor
@Log4j2
public class TenantScopedExecutionServiceImpl implements TenantScopedExecutionService {

private final FolioModuleMetadata moduleMetadata;
private final FolioExecutionContext executionContext;

@Override
public <T> T execute(String tenantId, Callable<T> action) {
log.info("execute:: tenantId: {}", tenantId);
Map<String, Collection<String>> headers = executionContext.getAllHeaders();
headers.put(XOkapiHeaders.TENANT, List.of(tenantId));

try (var x = new FolioExecutionContextSetter(moduleMetadata, headers)) {
return action.call();
} catch (Exception e) {
log.error("execute:: tenantId: {}", tenantId, e);
throw new TenantScopedExecutionException(e, tenantId);
}
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ folio:
enabled: true
environment: ${ENV:folio}
okapi-url: ${OKAPI_URL:http://okapi:9130}
logging:
feign:
enabled: true
level: full
management:
endpoints:
web:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/db/changelog/changes/initial_schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<column name="requester_id" type="uuid"/>
<column name="request_type" type="varchar(255)"/>
<column name="request_level" type="varchar(255)"/>
<column name="request_date" type="timestamp with time zone"/>
<column name="request_expiration_date" type="timestamp with time zone"/>
<column name="patron_comments" type="varchar(255)"/>
<column name="fulfillment_preference" type="varchar(255)"/>
Expand Down
15 changes: 15 additions & 0 deletions src/main/resources/log4j2.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
status = error
name = PropertiesConfig
packages = org.folio.spring.logging

appenders = console

appender.console.type = Console
appender.console.name = STDOUT

appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{HH:mm:ss} [$${folio:requestid:-}] [$${folio:tenantid:-}] [$${folio:userid:-}] [$${folio:moduleid:-}] %-5p %-20.20C{1} %m%n

rootLogger.level = debug
rootLogger.appenderRefs = debug
rootLogger.appenderRef.stdout.ref = STDOUT
4 changes: 3 additions & 1 deletion src/main/resources/swagger.api/ecs-tlr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ components:
ecs-tlr:
$ref: 'schemas/EcsTlr.yaml#/EcsTlr'
errorResponse:
$ref: schemas/errors.json
$ref: 'schemas/errors.json'
request:
$ref: 'schemas/request.json'
parameters:
requestId:
name: requestId
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/swagger.api/schemas/EcsTlr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ EcsTlr:
description: "Date when the request expires"
type: string
format: date-time
requestDate:
description: "Date when the request was placed"
type: string
format: date-time
patronComments:
description: "Comments made by the patron"
type: string
Expand All @@ -42,3 +46,4 @@ EcsTlr:
- requestType
- requestLevel
- fulfillmentPreference
- requestDate
57 changes: 57 additions & 0 deletions src/main/resources/swagger.api/schemas/override-blocks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"description": "Blocks to override (e.g. during checkout or renewal)",
"properties": {
"itemNotLoanableBlock": {
"description": "'Item not loanable' block",
"type": "object",
"properties": {
"dueDate": {
"description": "Due date for a new loan",
"type": "string",
"format": "date-time"
}
},
"additionalProperties": false,
"required": [
"dueDate"
]
},
"patronBlock": {
"description": "Automated patron block",
"type": "object",
"additionalProperties": false
},
"itemLimitBlock": {
"description": "Item limit block",
"type": "object",
"additionalProperties": false
},
"renewalBlock": {
"description": "Renewal block",
"type": "object",
"additionalProperties": false
},
"renewalDueDateRequiredBlock": {
"description": "Override renewal block which requires due date field",
"type": "object",
"properties": {
"dueDate": {
"description": "Due date for a new loan",
"type": "string",
"format": "date-time"
}
},
"additionalProperties": false,
"required": [
"dueDate"
]
},
"comment": {
"description": "Reason for override",
"type": "string"
}
},
"additionalProperties": false
}
35 changes: 35 additions & 0 deletions src/main/resources/swagger.api/schemas/request-search-index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request fields used for search",
"type": "object",
"properties": {
"callNumberComponents": {
"type": "object",
"description": "Effective call number components",
"properties": {
"callNumber": {
"type": "string",
"description": "Effective Call Number is an identifier assigned to an item or its holding and associated with the item."
},
"prefix": {
"type": "string",
"description": "Effective Call Number Prefix is the prefix of the identifier assigned to an item or its holding and associated with the item."
},
"suffix": {
"type": "string",
"description": "Effective Call Number Suffix is the suffix of the identifier assigned to an item or its holding and associated with the item."
}
},
"additionalProperties": false
},
"shelvingOrder": {
"type": "string",
"description": "A system generated normalization of the call number that allows for call number sorting in reports and search results"
},
"pickupServicePointName": {
"description": "The name of the request pickup service point",
"type": "string"
}
},
"additionalProperties": false
}
Loading

0 comments on commit 6b4093b

Please sign in to comment.