Skip to content

Commit

Permalink
MODEXPW-493 - Outdated preview of matched records in case of remove (…
Browse files Browse the repository at this point in the history
…add) affiliation and upload the same file with Holdings, Items identifiers in Central tenant (#585)

* MODEXPW-493 - get permitted and affiliated tenants

* MODEXPW-493 - updates tests

* MODEXPW-493 - updates tests

* MODEXPW-493 - testGetAffiliatedTenants

* MODEXPW-493 - update not match found logic for holdings

* MODEXPW-493 - update error service logs

* MODEXPW-493 - update tests

* MODEXPW-493 - fix sonar

* MODEXPW-493 - update tests

* MODEXPW-493 - update tests

* MODEXPW-493 - update tests

* MODEXPW-493 - update tests

* MODEXPW-493 - consortia bean

* MODEXPW-493 - not found match for item

* MODEXPW-493 - remove exception

* MODEXPW-493 - catch feign exception

* MODEXPW-493 - update module descriptor

* MODEXPW-493 - add unsupported exception
  • Loading branch information
alekGbuz authored Oct 29, 2024
1 parent b96088b commit d991a4f
Show file tree
Hide file tree
Showing 16 changed files with 399 additions and 110 deletions.
7 changes: 6 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@
{
"id": "permissions-users",
"version": "1.0"
},
{
"id": "consortia",
"version": "1.1"
}
],
"provides": [
Expand Down Expand Up @@ -212,7 +216,8 @@
"consortium-search.holdings.batch.collection.get",
"consortium-search.items.batch.collection.get",
"consortium-search.items.collection.get",
"bulk-edit.permissions-self-check.get"
"bulk-edit.permissions-self-check.get",
"consortia.user-tenants.collection.get"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
import static org.folio.dew.utils.BulkEditProcessorHelper.resolveIdentifier;
import static org.folio.dew.utils.Constants.DUPLICATES_ACROSS_TENANTS;
import static org.folio.dew.utils.Constants.MULTIPLE_MATCHES_MESSAGE;
import static org.folio.dew.utils.Constants.NO_HOLDING_AFFILIATION;
import static org.folio.dew.utils.Constants.NO_HOLDING_VIEW_PERMISSIONS;
import static org.folio.dew.utils.Constants.NO_MATCH_FOUND_MESSAGE;
import static org.folio.dew.utils.SearchIdentifierTypeResolver.getSearchIdentifierType;

import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FilenameUtils;
Expand All @@ -35,13 +33,13 @@
import org.folio.dew.domain.dto.IdentifierType;
import org.folio.dew.domain.dto.ItemIdentifier;
import org.folio.dew.error.BulkEditException;
import org.folio.dew.exceptions.ReadPermissionDoesNotExist;
import org.folio.dew.service.ConsortiaService;
import org.folio.dew.service.FolioExecutionContextManager;
import org.folio.dew.service.HoldingsReferenceService;
import org.folio.dew.service.mapper.HoldingsMapper;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.scope.FolioExecutionContextSetter;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -66,7 +64,10 @@ public class BulkEditHoldingsProcessor extends FolioExecutionContextManager impl
private final FolioExecutionContext folioExecutionContext;
private final UserClient userClient;
private final PermissionsValidator permissionsValidator;
private final TenantResolver tenantResolver;

@Value("#{stepExecution.jobExecution}")
private JobExecution jobExecution;
@Value("#{jobParameters['identifierType']}")
private String identifierType;
@Value("#{jobParameters['jobId']}")
Expand All @@ -85,11 +86,6 @@ public synchronized List<HoldingsFormat> process(ItemIdentifier itemIdentifier)
identifiersToCheckDuplication.add(itemIdentifier);

var holdings = getHoldingsRecords(itemIdentifier);
if (holdings.getExtendedHoldingsRecords().isEmpty()) {
log.error(NO_MATCH_FOUND_MESSAGE);
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}

var distinctHoldings = holdings.getExtendedHoldingsRecords().stream()
.filter(holdingsRecord -> !fetchedHoldingsIds.contains(holdingsRecord.getEntity().getId()))
.toList();
Expand Down Expand Up @@ -126,38 +122,36 @@ private ExtendedHoldingsRecordCollection getHoldingsRecords(ItemIdentifier itemI
if (INSTANCEHRID != identifierTypeEnum && tenantIds.size() > 1) {
throw new BulkEditException(DUPLICATES_ACROSS_TENANTS);
}
tenantIds.forEach(tenantId -> {
var affiliatedPermittedTenants = tenantResolver.getAffiliatedPermittedTenantIds(EntityType.HOLDINGS_RECORD,
jobExecution, identifierType, tenantIds, itemIdentifier);
affiliatedPermittedTenants.forEach(tenantId -> {
try (var context = new FolioExecutionContextSetter(refreshAndGetFolioExecutionContext(tenantId, folioExecutionContext))) {
permissionsValidator.checkBulkEditReadPermissions(tenantId, EntityType.HOLDINGS_RECORD);
var holdingsRecordCollection = getHoldingsRecordCollection(type, itemIdentifier);
extendedHoldingsRecordCollection.getExtendedHoldingsRecords().addAll(
holdingsRecordCollection.getHoldingsRecords().stream()
.map(holdingsRecord -> new ExtendedHoldingsRecord().tenantId(tenantId).entity(holdingsRecord)).toList()
);
extendedHoldingsRecordCollection.setTotalRecords(extendedHoldingsRecordCollection.getTotalRecords() + holdingsRecordCollection.getTotalRecords());
} catch (Exception e) {
if (e instanceof FeignException && ((FeignException) e).status() == 401) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
throw new BulkEditException(format(NO_HOLDING_AFFILIATION, user.getUsername(), resolveIdentifier(identifierType), identifier, tenantId));
} else if (e instanceof ReadPermissionDoesNotExist) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
throw new BulkEditException(format(NO_HOLDING_VIEW_PERMISSIONS, user.getUsername(), resolveIdentifier(identifierType), identifier, tenantId));
} else {
throw e;
}
log.error(e.getMessage());
throw e;
}
});
return extendedHoldingsRecordCollection;
return extendedHoldingsRecordCollection;
} else {
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
} else {
// Process local tenant case
checkReadPermissions(folioExecutionContext.getTenantId(), identifier);
var holdingsRecordCollection = getHoldingsRecordCollection(type, itemIdentifier);
return new ExtendedHoldingsRecordCollection().extendedHoldingsRecords(holdingsRecordCollection.getHoldingsRecords().stream()
var extendedHoldingsRecordCollection = new ExtendedHoldingsRecordCollection().extendedHoldingsRecords(holdingsRecordCollection.getHoldingsRecords().stream()
.map(holdingsRecord -> new ExtendedHoldingsRecord().tenantId(folioExecutionContext.getTenantId()).entity(holdingsRecord)).toList())
.totalRecords(holdingsRecordCollection.getTotalRecords());
if (extendedHoldingsRecordCollection.getExtendedHoldingsRecords().isEmpty()) {
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
return extendedHoldingsRecordCollection;
}
}

Expand Down Expand Up @@ -189,5 +183,4 @@ private HoldingsRecordCollection getHoldingsRecordCollection(IdentifierType type
throw new BulkEditException(format("Identifier type \"%s\" is not supported", identifierType));
}
}

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
package org.folio.dew.batch.bulkedit.jobs;

import static org.folio.dew.utils.Constants.NO_MATCH_FOUND_MESSAGE;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.folio.dew.client.SearchClient;
import org.folio.dew.domain.dto.BatchIdsDto;
import org.folio.dew.domain.dto.ConsortiumItem;
import org.folio.dew.domain.dto.ExtendedItemCollection;
import org.folio.dew.domain.dto.Item;
import org.folio.dew.domain.dto.ItemCollection;
import org.folio.dew.domain.dto.ItemFormat;
import org.folio.dew.error.BulkEditException;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;
Expand All @@ -28,10 +20,6 @@ public class BulkEditItemListProcessor implements ItemProcessor<ExtendedItemColl

@Override
public List<ItemFormat> process(ExtendedItemCollection extendedItemCollection) {
if (extendedItemCollection.getExtendedItems().isEmpty()) {
log.error(NO_MATCH_FOUND_MESSAGE);
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
return extendedItemCollection.getExtendedItems().stream()
.map(bulkEditItemProcessor::process)
.toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.folio.dew.batch.bulkedit.jobs;

import static java.lang.String.format;
import static org.folio.dew.utils.BulkEditProcessorHelper.resolveIdentifier;
import static org.folio.dew.utils.Constants.FILE_NAME;
import static org.folio.dew.utils.Constants.NO_HOLDING_AFFILIATION;
import static org.folio.dew.utils.Constants.NO_HOLDING_VIEW_PERMISSIONS;
import static org.folio.dew.utils.Constants.NO_ITEM_AFFILIATION;
import static org.folio.dew.utils.Constants.NO_ITEM_VIEW_PERMISSIONS;

import feign.FeignException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.folio.dew.batch.bulkedit.jobs.permissions.check.PermissionsValidator;
import org.folio.dew.client.UserClient;
import org.folio.dew.domain.dto.EntityType;
import org.folio.dew.domain.dto.ItemIdentifier;
import org.folio.dew.domain.dto.JobParameterNames;
import org.folio.dew.error.BulkEditException;
import org.folio.dew.service.BulkEditProcessingErrorsService;
import org.folio.dew.service.ConsortiaService;
import org.folio.spring.FolioExecutionContext;
import org.springframework.batch.core.JobExecution;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

@Component
@RequiredArgsConstructor
public class TenantResolver {

private static final String UNSUPPORTED_ERROR_MESSAGE_FOR_AFFILIATIONS = "Unsupported entity type to get affiliation error message";
private static final String UNSUPPORTED_ERROR_MESSAGE_FOR_PERMISSIONS = "Unsupported entity type to get permissions error message";

private final FolioExecutionContext folioExecutionContext;
private final ConsortiaService consortiaService;
private final PermissionsValidator permissionsValidator;
private final BulkEditProcessingErrorsService bulkEditProcessingErrorsService;
private final UserClient userClient;

public Set<String> getAffiliatedPermittedTenantIds(EntityType entityType, JobExecution jobExecution, String identifierType, Set<String> tenantIds, ItemIdentifier itemIdentifier) {
var affiliatedTenants = consortiaService.getAffiliatedTenants(folioExecutionContext.getTenantId(), folioExecutionContext.getUserId().toString());
var jobId = jobExecution.getJobParameters().getString(JobParameterNames.JOB_ID);
var fileName = FilenameUtils.getName(jobExecution.getJobParameters().getString(FILE_NAME));
var affiliatedAndPermittedTenants = new HashSet<String>();
for (var tenantId : tenantIds) {
if (!affiliatedTenants.contains(tenantId)) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
var errorMessage = format(getAffiliationErrorPlaceholder(entityType), user.getUsername(),
resolveIdentifier(identifierType), itemIdentifier.getItemId(), tenantId);
bulkEditProcessingErrorsService.saveErrorInCSV(jobId, itemIdentifier.getItemId(), errorMessage, fileName);
} else if (!isBulkEditReadPermissionExists(tenantId, entityType)) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
var errorMessage = format(getViewPermissionErrorPlaceholder(entityType), user.getUsername(),
resolveIdentifier(identifierType), itemIdentifier.getItemId(), tenantId);
bulkEditProcessingErrorsService.saveErrorInCSV(jobId, itemIdentifier.getItemId(), errorMessage, fileName);
} else {
affiliatedAndPermittedTenants.add(tenantId);
}
}
return affiliatedAndPermittedTenants;
}

private boolean isBulkEditReadPermissionExists(String tenantId, EntityType entityType) {
try {
return permissionsValidator.isBulkEditReadPermissionExists(tenantId, entityType);
} catch (FeignException e) {
throw new BulkEditException(e.getMessage());
}
}

protected String getAffiliationErrorPlaceholder(EntityType entityType) {
return switch (entityType) {
case ITEM -> NO_ITEM_AFFILIATION;
case HOLDINGS_RECORD -> NO_HOLDING_AFFILIATION;
default -> throw new UnsupportedOperationException(UNSUPPORTED_ERROR_MESSAGE_FOR_AFFILIATIONS);
};
}

protected String getViewPermissionErrorPlaceholder(EntityType entityType) {
return switch (entityType) {
case ITEM -> NO_ITEM_VIEW_PERMISSIONS;
case HOLDINGS_RECORD -> NO_HOLDING_VIEW_PERMISSIONS;
default -> throw new UnsupportedOperationException(UNSUPPORTED_ERROR_MESSAGE_FOR_PERMISSIONS);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.dew.domain.dto.EntityType;
import org.folio.dew.exceptions.ReadPermissionDoesNotExist;
import org.folio.spring.FolioExecutionContext;
import org.springframework.stereotype.Component;

Expand All @@ -19,12 +18,6 @@ public class PermissionsValidator {
private final RequiredPermissionResolver requiredPermissionResolver;
private final FolioExecutionContext folioExecutionContext;

public void checkBulkEditReadPermissions(String tenantId, EntityType entityType) {
if (!isBulkEditReadPermissionExists(tenantId, entityType)) {
throw new ReadPermissionDoesNotExist();
}
}

public boolean isBulkEditReadPermissionExists(String tenantId, EntityType entityType) {
var readPermissionForEntity = requiredPermissionResolver.getReadPermission(entityType);
var userPermissions = permissionsProvider.getUserPermissions(tenantId, folioExecutionContext.getUserId().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@
import static org.folio.dew.utils.BulkEditProcessorHelper.resolveIdentifier;
import static org.folio.dew.utils.Constants.DUPLICATES_ACROSS_TENANTS;
import static org.folio.dew.utils.Constants.MULTIPLE_MATCHES_MESSAGE;
import static org.folio.dew.utils.Constants.NO_ITEM_AFFILIATION;
import static org.folio.dew.utils.Constants.NO_ITEM_VIEW_PERMISSIONS;
import static org.folio.dew.utils.Constants.NO_MATCH_FOUND_MESSAGE;
import static org.folio.dew.utils.SearchIdentifierTypeResolver.getSearchIdentifierType;

import feign.FeignException;
import feign.codec.DecodeException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.folio.dew.batch.bulkedit.jobs.TenantResolver;
import org.folio.dew.batch.bulkedit.jobs.permissions.check.PermissionsValidator;
import org.folio.dew.client.InventoryClient;
import org.folio.dew.client.SearchClient;
Expand All @@ -30,12 +29,12 @@
import org.folio.dew.domain.dto.IdentifierType;
import org.folio.dew.domain.dto.ItemIdentifier;
import org.folio.dew.error.BulkEditException;
import org.folio.dew.exceptions.ReadPermissionDoesNotExist;
import org.folio.dew.service.ConsortiaService;
import org.folio.dew.service.FolioExecutionContextManager;
import org.folio.dew.utils.ExceptionHelper;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.scope.FolioExecutionContextSetter;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -58,7 +57,10 @@ public class ItemFetcher extends FolioExecutionContextManager implements ItemPro
private final UserClient userClient;
private final PermissionsValidator permissionsValidator;
private final FolioExecutionContext folioExecutionContext;
private final TenantResolver tenantResolver;

@Value("#{stepExecution.jobExecution}")
private JobExecution jobExecution;
@Value("#{jobParameters['identifierType']}")
private String identifierType;

Expand Down Expand Up @@ -93,9 +95,10 @@ public synchronized ExtendedItemCollection process(ItemIdentifier itemIdentifier
if (HOLDINGSRECORDID != identifierTypeEnum && tenantIds.size() > 1) {
throw new BulkEditException(DUPLICATES_ACROSS_TENANTS);
}
tenantIds.forEach(tenantId -> {
var affiliatedPermittedTenants = tenantResolver.getAffiliatedPermittedTenantIds(EntityType.ITEM,
jobExecution, identifierType, tenantIds, itemIdentifier);
affiliatedPermittedTenants.forEach(tenantId -> {
try (var context = new FolioExecutionContextSetter(refreshAndGetFolioExecutionContext(tenantId, folioExecutionContext))) {
permissionsValidator.checkBulkEditReadPermissions(tenantId, EntityType.ITEM);
var url = format(getMatchPattern(identifierType), idType, identifier);
var itemCollection = inventoryClient.getItemByQuery(url, Integer.MAX_VALUE);
if (itemCollection.getItems().size() > limit) {
Expand All @@ -107,15 +110,9 @@ public synchronized ExtendedItemCollection process(ItemIdentifier itemIdentifier
);
extendedItemCollection.setTotalRecords(extendedItemCollection.getTotalRecords() + itemCollection.getTotalRecords());
} catch (Exception e) {
if (e instanceof FeignException && ((FeignException) e).status() == 401) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
throw new BulkEditException(format(NO_ITEM_AFFILIATION, user.getUsername(), idType, identifier, tenantId));
} else if (e instanceof ReadPermissionDoesNotExist) {
var user = userClient.getUserById(folioExecutionContext.getUserId().toString());
throw new BulkEditException(format(NO_ITEM_VIEW_PERMISSIONS, user.getUsername(), resolveIdentifier(identifierType), identifier, tenantId));
}
throw e;
}
log.error(e.getMessage());
throw e;
}
});
} else {
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
Expand All @@ -133,6 +130,10 @@ public synchronized ExtendedItemCollection process(ItemIdentifier itemIdentifier
extendedItemCollection.setExtendedItems(itemCollection.getItems().stream()
.map(item -> new ExtendedItem().tenantId(folioExecutionContext.getTenantId()).entity(item)).toList());
extendedItemCollection.setTotalRecords(itemCollection.getTotalRecords());
if (extendedItemCollection.getExtendedItems().isEmpty()) {
log.error(NO_MATCH_FOUND_MESSAGE);
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
}
return extendedItemCollection;
} catch (DecodeException e) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/folio/dew/client/ConsortiumClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.folio.dew.client;

import org.folio.dew.domain.bean.ConsortiaCollection;
import org.folio.dew.domain.dto.UserTenantCollection;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "consortia")
public interface ConsortiumClient {

@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
ConsortiaCollection getConsortia();

@GetMapping(value = "/{consortiumId}/user-tenants", produces = MediaType.APPLICATION_JSON_VALUE)
UserTenantCollection getConsortiaUserTenants(@PathVariable String consortiumId, @RequestParam String userId, @RequestParam int limit);
}
12 changes: 12 additions & 0 deletions src/main/java/org/folio/dew/domain/bean/Consortia.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.folio.dew.domain.bean;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Consortia {

private String id;
}
Loading

0 comments on commit d991a4f

Please sign in to comment.