Skip to content

Commit

Permalink
MODTLR-141: Create shadow instance in primary request tenant (#104)
Browse files Browse the repository at this point in the history
* MODTLR-141 Create shadow instance in primary request tenant

* MODTLR-141 Replace InventoryItem with Item

* MODTLR-141 Share instance through central tenant

* MODTLR-141 Fix TenantContext constructor

* MODTLR-141 Call instance sharing API in central tenant without system user

* MODTLR-141 Call instance sharing API in central tenant without system user

* MODTLR-141 Log and throw sharing error

* MODTLR-141 Call instance sharing API in target tenant

* MODTLR-141 Call instance sharing API in target tenant

* MODTLR-141 Refactoring

* MODTLR-141 Fix build

* MODTLR-141 Extend test

* MODTLR-141 Remove redundant import

* MODTLR-141 Fix ecs-tlr.yaml

* MODTLR-141 Tests for ConsortiumService

* MODTLR-141 Refactoring ConsortiumService

* MODTLR-141 Refactoring

* MODTLR-141 Unit test for instance sharing failure

* MODTLR-141 Remove instance sharing permission from module permissions

(cherry picked from commit fd6a42a)
  • Loading branch information
OleksandrVidinieiev committed Feb 18, 2025
1 parent a7e597c commit 376a00e
Show file tree
Hide file tree
Showing 22 changed files with 619 additions and 48 deletions.
3 changes: 2 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,8 @@
"circulation.rules.request-policy.get",
"mod-settings.entries.item.get",
"mod-settings.entries.collection.get",
"mod-settings.global.read.circulation"
"mod-settings.global.read.circulation",
"consortia.sharing-instances.item.post"
]
}
},
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/folio/client/feign/ConsortiaClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.folio.domain.dto.PublicationRequest;
import org.folio.domain.dto.PublicationResponse;
import org.folio.domain.dto.SharingInstance;
import org.folio.domain.dto.TenantCollection;
import org.folio.spring.config.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
Expand All @@ -20,4 +21,8 @@ public interface ConsortiaClient {
@PostMapping(value = "/{consortiumId}/publications", consumes = MediaType.APPLICATION_JSON_VALUE)
PublicationResponse postPublications(@PathVariable String consortiumId,
@RequestBody PublicationRequest publicationRequest);

@PostMapping(value = "/{consortiumId}/sharing/instances", produces = MediaType.APPLICATION_JSON_VALUE)
SharingInstance shareInstance(@PathVariable String consortiumId,
@RequestBody SharingInstance sharingInstance);
}
4 changes: 2 additions & 2 deletions src/main/java/org/folio/client/feign/InstanceClient.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.folio.client.feign;

import org.folio.domain.dto.Instance;
import org.folio.domain.dto.Instances;
import org.folio.domain.dto.InventoryInstance;
import org.folio.spring.config.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -11,6 +11,6 @@
public interface InstanceClient extends GetByQueryClient<Instances> {

@GetMapping("/{id}")
InventoryInstance get(@PathVariable String id);
Instance get(@PathVariable String id);

}
4 changes: 2 additions & 2 deletions src/main/java/org/folio/client/feign/ItemClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.folio.client.feign;

import org.folio.domain.dto.InventoryItem;
import org.folio.domain.dto.Item;
import org.folio.domain.dto.Items;
import org.folio.spring.config.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
Expand All @@ -11,5 +11,5 @@
public interface ItemClient extends GetByQueryClient<Items> {

@GetMapping("/{id}")
InventoryItem get(@PathVariable String id);
Item get(@PathVariable String id);
}
2 changes: 2 additions & 0 deletions src/main/java/org/folio/service/ConsortiaService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import java.util.Collection;

import org.folio.domain.dto.SharingInstance;
import org.folio.domain.dto.Tenant;
import org.folio.domain.dto.TenantCollection;

public interface ConsortiaService {
TenantCollection getAllConsortiumTenants(String consortiumId);
Collection<Tenant> getAllConsortiumTenants();
String getCentralTenantId();
SharingInstance shareInstance(String instanceId, String targetTenantId);
}
9 changes: 9 additions & 0 deletions src/main/java/org/folio/service/ConsortiumService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.folio.service;

public interface ConsortiumService {
String getCurrentTenantId();
String getCurrentConsortiumId();
String getCentralTenantId();
boolean isCurrentTenantCentral();
boolean isCentralTenant(String tenantId);
}
2 changes: 2 additions & 0 deletions src/main/java/org/folio/service/InventoryService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.folio.service;

import java.util.Collection;
import java.util.Optional;

import org.folio.domain.dto.Campus;
import org.folio.domain.dto.HoldingsRecord;
Expand All @@ -17,6 +18,7 @@ public interface InventoryService {
Collection<HoldingsRecord> findHoldings(CqlQuery query, String idIndex, Collection<String> ids);
Collection<HoldingsRecord> findHoldings(Collection<String> ids);
Collection<Instance> findInstances(Collection<String> ids);
Optional<Instance> findInstance(String instanceId);
Collection<MaterialType> findMaterialTypes(Collection<String> ids);
Collection<LoanType> findLoanTypes(Collection<String> ids);
Collection<Library> findLibraries(Collection<String> ids);
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/folio/service/RequestService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import org.folio.domain.RequestWrapper;
import org.folio.domain.dto.CirculationItem;
import org.folio.domain.dto.InventoryInstance;
import org.folio.domain.dto.InventoryItem;
import org.folio.domain.dto.Instance;
import org.folio.domain.dto.Item;
import org.folio.domain.dto.ReorderQueue;
import org.folio.domain.dto.Request;
import org.folio.support.CqlQuery;
Expand All @@ -25,8 +25,8 @@ RequestWrapper createIntermediateRequest(Request intermediateRequest,
CirculationItem updateCirculationItemOnRequestCreation(CirculationItem circulationItem,
Request secondaryRequest);

InventoryItem getItemFromStorage(String itemId, String tenantId);
InventoryInstance getInstanceFromStorage(String instanceId, String tenantId);
Item getItemFromStorage(String itemId, String tenantId);
Instance getInstanceFromStorage(String instanceId, String tenantId);
Request getRequestFromStorage(String requestId, String tenantId);
Request getRequestFromStorage(String requestId);
Collection<Request> getRequestsFromStorage(CqlQuery query, String idIndex, Collection<String> ids);
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.folio.client.feign.ConsortiaClient;
import org.folio.client.feign.ConsortiaConfigurationClient;
import org.folio.domain.dto.ConsortiaConfiguration;
import org.folio.domain.dto.SharingInstance;
import org.folio.domain.dto.Status;
import org.folio.domain.dto.Tenant;
import org.folio.domain.dto.TenantCollection;
import org.folio.domain.dto.UserTenant;
import org.folio.service.ConsortiaService;
import org.folio.service.ConsortiumService;
import org.folio.service.UserTenantsService;
import org.folio.spring.service.SystemUserScopedExecutionService;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
Expand All @@ -25,6 +30,8 @@ public class ConsortiaServiceImpl implements ConsortiaService {
private final ConsortiaClient consortiaClient;
private final ConsortiaConfigurationClient consortiaConfigurationClient;
private final UserTenantsService userTenantsService;
private final ConsortiumService consortiumService;
private final SystemUserScopedExecutionService systemUserService;

@Override
public TenantCollection getAllConsortiumTenants(String consortiumId) {
Expand Down Expand Up @@ -53,4 +60,27 @@ public String getCentralTenantId() {
log.info("getCentralTenantId:: central tenant ID: {}", centralTenantId);
return centralTenantId;
}

@Override
public SharingInstance shareInstance(String instanceId, String targetTenantId) {
log.info("shareInstance:: sharing instance {} with tenant {}", instanceId, targetTenantId);

SharingInstance sharingRequest = new SharingInstance()
.instanceIdentifier(UUID.fromString(instanceId))
.sourceTenantId(consortiumService.getCentralTenantId())
.targetTenantId(targetTenantId);

SharingInstance sharingResponse = consortiaClient.shareInstance(
consortiumService.getCurrentConsortiumId(), sharingRequest);

Status sharingStatus = sharingResponse.getStatus();
log.info("shareInstance:: sharing status: {}", sharingStatus);
if (Status.ERROR == sharingStatus) {
log.error("shareInstance:: instance sharing failed: {}", sharingResponse::getError);
throw new IllegalStateException(String.format("Failed to share instance %s with tenant %s: %s",
instanceId, targetTenantId, sharingResponse.getError()));
}

return sharingResponse;
}
}
79 changes: 79 additions & 0 deletions src/main/java/org/folio/service/impl/ConsortiumServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.folio.service.impl;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.folio.service.ConsortiumService;
import org.folio.service.UserTenantsService;
import org.folio.spring.FolioExecutionContext;
import org.springframework.stereotype.Service;

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

@Service
@RequiredArgsConstructor
@Log4j2
public class ConsortiumServiceImpl implements ConsortiumService {

private static final Map<String, TenantContext> CACHE = new ConcurrentHashMap<>();

private final UserTenantsService userTenantsService;
private final FolioExecutionContext folioContext;

@Override
public String getCurrentTenantId() {
return folioContext.getTenantId();
}

@Override
public String getCurrentConsortiumId() {
return getTenantContext(getCurrentTenantId())
.consortiumId();
}

@Override
public String getCentralTenantId() {
return getTenantContext(getCurrentTenantId())
.centralTenantId();
}

@Override
public boolean isCurrentTenantCentral() {
return isCentralTenant(getCurrentTenantId());
}

@Override
public boolean isCentralTenant(String tenantId) {
return getCentralTenantId().equals(tenantId);
}

private TenantContext getTenantContext(String tenantId) {
TenantContext tenantContext = CACHE.get(tenantId);
if (tenantContext != null) {
log.info("getTenantContext:: cache hit: {}", tenantContext);
} else {
log.info("getTenantContext:: cache miss for tenant {}", tenantId);
tenantContext = buildTenantContext(tenantId);
log.info("getTenantContext:: caching: {}", tenantContext);
CACHE.put(tenantId, tenantContext);
}
log.debug("getTenantContext:: cache: {}", CACHE);
return tenantContext;
}

private TenantContext buildTenantContext(String tenantId) {
return Optional.ofNullable(userTenantsService.findFirstUserTenant())
.map(ut -> new TenantContext(tenantId, ut.getConsortiumId(), ut.getCentralTenantId()))
.orElseThrow(() -> new IllegalStateException("Failed to fetch user tenant"));
}

public static void clearCache() {
log.info("clearCache:: clearing cache");
CACHE.clear();
}

private record TenantContext(String tenantId, String consortiumId, String centralTenantId) { }

}
15 changes: 15 additions & 0 deletions src/main/java/org/folio/service/impl/InventoryServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.folio.service.impl;

import java.util.Collection;
import java.util.Optional;

import org.folio.client.feign.HoldingClient;
import org.folio.client.feign.InstanceClient;
Expand Down Expand Up @@ -31,6 +32,7 @@
import org.folio.support.CqlQuery;
import org.springframework.stereotype.Service;

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

Expand Down Expand Up @@ -78,6 +80,19 @@ public Collection<Instance> findInstances(Collection<String> ids) {
return BulkFetcher.fetch(instanceClient, ids, Instances::getInstances);
}

@Override
public Optional<Instance> findInstance(String instanceId) {
log.info("findInstance:: searching instance {}", instanceId);
try {
Instance instance = instanceClient.get(instanceId);
log.info("findInstance:: instance {} found", instanceId);
return Optional.of(instance);
} catch (FeignException.NotFound e) {
log.warn("findInstance:: instance {} not found", instanceId);
return Optional.empty();
}
}

@Override
public Collection<MaterialType> findMaterialTypes(Collection<String> ids) {
log.info("findMaterialTypes:: searching material types by {} IDs", ids::size);
Expand Down
42 changes: 34 additions & 8 deletions src/main/java/org/folio/service/impl/RequestServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
import org.folio.domain.RequestWrapper;
import org.folio.domain.dto.CirculationItem;
import org.folio.domain.dto.CirculationItemStatus;
import org.folio.domain.dto.InventoryInstance;
import org.folio.domain.dto.InventoryItem;
import org.folio.domain.dto.InventoryItemStatus;
import org.folio.domain.dto.Instance;
import org.folio.domain.dto.Item;
import org.folio.domain.dto.ItemStatus;
import org.folio.domain.dto.ReorderQueue;
import org.folio.domain.dto.Request;
import org.folio.domain.dto.Requests;
import org.folio.domain.dto.ServicePoint;
import org.folio.domain.dto.User;
import org.folio.exception.RequestCreatingException;
import org.folio.service.CloningService;
import org.folio.service.ConsortiaService;
import org.folio.service.ConsortiumService;
import org.folio.service.InventoryService;
import org.folio.service.RequestService;
import org.folio.service.ServicePointService;
import org.folio.service.UserService;
Expand Down Expand Up @@ -53,6 +56,9 @@ public class RequestServiceImpl implements RequestService {
private final CloningService<User> userCloningService;
private final CloningService<ServicePoint> servicePointCloningService;
private final SystemUserScopedExecutionService systemUserScopedExecutionService;
private final ConsortiaService consortiaService;
private final ConsortiumService consortiumService;
private final InventoryService inventoryService;

public static final String HOLDINGS_RECORD_ID = "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9";

Expand All @@ -64,6 +70,8 @@ public RequestWrapper createPrimaryRequest(Request primaryRequest,
log.info("createPrimaryRequest:: creating primary request {} in tenant {}", requestId,
primaryRequestTenantId);

createShadowInstance(primaryRequest.getInstanceId(), primaryRequestTenantId);

return executionService.executeSystemUserScoped(primaryRequestTenantId, () -> {
CirculationItem circItem = createCirculationItem(primaryRequest, secondaryRequestTenantId);
Request request = circulationClient.createRequest(primaryRequest);
Expand All @@ -75,6 +83,24 @@ public RequestWrapper createPrimaryRequest(Request primaryRequest,
});
}

private void createShadowInstance(String instanceId, String targetTenantId) {
log.info("createShadowInstance:: checking if instance must be shared with primary request tenant");
if (consortiumService.isCentralTenant(targetTenantId)) {
log.info("createShadowInstance:: tenant {} is central tenant, doing nothing", targetTenantId);
return;
}

systemUserScopedExecutionService.executeSystemUserScoped(targetTenantId, () -> {
if (inventoryService.findInstance(instanceId).isPresent()) {
log.info("createShadowInstance:: instance {} already exists in tenant {}, doing nothing",
instanceId, targetTenantId);
} else {
consortiaService.shareInstance(instanceId, targetTenantId);
}
return instanceId;
});
}

@Override
public RequestWrapper createSecondaryRequest(Request request, String primaryRequestTenantId,
Collection<String> secondaryRequestTenantIds) {
Expand Down Expand Up @@ -223,7 +249,7 @@ public CirculationItem createCirculationItem(Request request, String inventoryTe
return circulationItemClient.updateCirculationItem(itemId, existingCirculationItem);
}

InventoryInstance instance = getInstanceFromStorage(instanceId, inventoryTenantId);
Instance instance = getInstanceFromStorage(instanceId, inventoryTenantId);
var circulationItem = new CirculationItem()
.id(UUID.fromString(itemId))
.holdingsRecordId(UUID.fromString(HOLDINGS_RECORD_ID))
Expand All @@ -245,9 +271,9 @@ public CirculationItem createCirculationItem(Request request, String inventoryTe
}

private CirculationItemStatus.NameEnum defineCirculationItemStatus(
InventoryItemStatus.NameEnum itemStatus) {
ItemStatus.NameEnum itemStatus) {

return itemStatus == InventoryItemStatus.NameEnum.PAGED
return itemStatus == ItemStatus.NameEnum.PAGED
? CirculationItemStatus.NameEnum.AVAILABLE
: CirculationItemStatus.NameEnum.fromValue(itemStatus.getValue());
}
Expand All @@ -274,14 +300,14 @@ public CirculationItem updateCirculationItemOnRequestCreation(CirculationItem ci
}

@Override
public InventoryItem getItemFromStorage(String itemId, String tenantId) {
public Item getItemFromStorage(String itemId, String tenantId) {
log.info("getItemFromStorage:: Fetching item {} from tenant {}", itemId, tenantId);
return systemUserScopedExecutionService.executeSystemUserScoped(tenantId,
() -> itemClient.get(itemId));
}

@Override
public InventoryInstance getInstanceFromStorage(String instanceId, String tenantId) {
public Instance getInstanceFromStorage(String instanceId, String tenantId) {
log.info("getInstanceFromStorage:: Fetching instance {} from tenant {}", instanceId, tenantId);
return systemUserScopedExecutionService.executeSystemUserScoped(tenantId,
() -> instanceClient.get(instanceId));
Expand Down
Loading

0 comments on commit 376a00e

Please sign in to comment.