Skip to content

Commit

Permalink
MODEXPW-477 - Items and holdings tenant populating (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
khandramai authored Jul 10, 2024
1 parent 84a4682 commit e96c2db
Show file tree
Hide file tree
Showing 37 changed files with 540 additions and 133 deletions.
5 changes: 4 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,10 @@
"inventory-storage.nature-of-content-terms.item.get",
"inventory-storage.instance-formats.item.get",
"inventory-storage.instance-note-types.item.get",
"source-storage.sourceRecords.get"
"source-storage.sourceRecords.get",
"user-tenants.collection.get",
"consortium-search.holdings.collection.get",
"consortium-search.items.collection.get"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,65 @@
package org.folio.dew.batch.bulkedit.jobs;

import static java.lang.String.format;
import static org.folio.dew.domain.dto.BatchIdsDto.IdentifierTypeEnum.INSTANCEHRID;
import static org.folio.dew.domain.dto.IdentifierType.HRID;
import static org.folio.dew.domain.dto.IdentifierType.ID;
import static org.folio.dew.domain.dto.IdentifierType.INSTANCE_HRID;
import static org.folio.dew.domain.dto.IdentifierType.ITEM_BARCODE;
import static org.folio.dew.utils.BulkEditProcessorHelper.getMatchPattern;
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_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;
import org.apache.commons.lang3.StringUtils;
import org.folio.dew.client.HoldingClient;
import org.folio.dew.client.SearchClient;
import org.folio.dew.client.UserClient;
import org.folio.dew.domain.dto.BatchIdsDto;
import org.folio.dew.domain.dto.ConsortiumHolding;
import org.folio.dew.domain.dto.ExtendedHoldingsRecord;
import org.folio.dew.domain.dto.ExtendedHoldingsRecordCollection;
import org.folio.dew.domain.dto.HoldingsFormat;
import org.folio.dew.domain.dto.HoldingsRecord;
import org.folio.dew.domain.dto.HoldingsRecordCollection;
import org.folio.dew.domain.dto.IdentifierType;
import org.folio.dew.domain.dto.ItemIdentifier;
import org.folio.dew.error.BulkEditException;
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.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@StepScope
@RequiredArgsConstructor
@Log4j2
public class BulkEditHoldingsProcessor implements ItemProcessor<ItemIdentifier, List<HoldingsFormat>> {
public class BulkEditHoldingsProcessor extends FolioExecutionContextManager implements ItemProcessor<ItemIdentifier, List<HoldingsFormat>> {
private final HoldingClient holdingClient;
private final HoldingsMapper holdingsMapper;
private final HoldingsReferenceService holdingsReferenceService;
private final SearchClient searchClient;
private final ConsortiaService consortiaService;
private final FolioExecutionContext folioExecutionContext;
private final UserClient userClient;

@Value("#{jobParameters['identifierType']}")
private String identifierType;
Expand All @@ -55,36 +79,87 @@ public List<HoldingsFormat> process(ItemIdentifier itemIdentifier) throws BulkEd
identifiersToCheckDuplication.add(itemIdentifier);

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

var distinctHoldings = holdings.getHoldingsRecords().stream()
.filter(holdingsRecord -> !fetchedHoldingsIds.contains(holdingsRecord.getId()))
var distinctHoldings = holdings.getExtendedHoldingsRecords().stream()
.filter(holdingsRecord -> !fetchedHoldingsIds.contains(holdingsRecord.getEntity().getId()))
.toList();
fetchedHoldingsIds.addAll(distinctHoldings.stream().map(HoldingsRecord::getId).toList());
fetchedHoldingsIds.addAll(distinctHoldings.stream().map(extendedHoldingsRecord -> extendedHoldingsRecord.getEntity().getId()).toList());

var instanceHrid = INSTANCE_HRID == IdentifierType.fromValue(identifierType) ? itemIdentifier.getItemId() : null;
var itemBarcode = ITEM_BARCODE == IdentifierType.fromValue(identifierType) ? itemIdentifier.getItemId() : null;

return distinctHoldings.stream()
.map(r -> holdingsMapper.mapToHoldingsFormat(r, itemIdentifier.getItemId(), jobId, FilenameUtils.getName(fileName)).withOriginal(r))
.map(extendedHoldingsRecord -> holdingsMapper.mapToHoldingsFormat(extendedHoldingsRecord, itemIdentifier.getItemId(), jobId, FilenameUtils.getName(fileName)).withOriginal(extendedHoldingsRecord.getEntity()))
.map(holdingsFormat -> holdingsFormat.withInstanceHrid(instanceHrid))
.map(holdingsFormat -> holdingsFormat.withItemBarcode(itemBarcode))
.toList();
}

private HoldingsRecordCollection getHoldingsRecords(ItemIdentifier itemIdentifier) {
return switch (IdentifierType.fromValue(identifierType)) {
case ID, HRID -> checkDuplicates(holdingClient.getHoldingsByQuery(
String.format(getMatchPattern(identifierType), resolveIdentifier(identifierType), itemIdentifier.getItemId())));
case INSTANCE_HRID ->
holdingClient.getHoldingsByQuery("instanceId==" + holdingsReferenceService.getInstanceIdByHrid(itemIdentifier.getItemId()), Integer.MAX_VALUE);
case ITEM_BARCODE ->
holdingClient.getHoldingsByQuery("id==" + holdingsReferenceService.getHoldingsIdByItemBarcode(itemIdentifier.getItemId()), 1);
default -> throw new BulkEditException(String.format("Identifier type \"%s\" is not supported", identifierType));
};
private ExtendedHoldingsRecordCollection getHoldingsRecords(ItemIdentifier itemIdentifier) {
var type = IdentifierType.fromValue(identifierType);
var identifier = itemIdentifier.getItemId();

if (StringUtils.isNotEmpty(consortiaService.getCentralTenantId())) {
// Process central tenant
var identifierTypeEnum = getSearchIdentifierType(type);
var consortiumHoldingsCollection = searchClient.getConsortiumHoldingCollection(new BatchIdsDto()
.identifierType(getSearchIdentifierType(type))
.ids(List.of(identifier)));
if (consortiumHoldingsCollection.getTotalRecords() > 0) {
var extendedHoldingsRecordCollection = new ExtendedHoldingsRecordCollection()
.extendedHoldingsRecords(new ArrayList<>())
.totalRecords(0);
var tenantIds = consortiumHoldingsCollection.getConsortiumHoldingRecords()
.stream()
.map(ConsortiumHolding::getTenantId).collect(Collectors.toSet());
if (INSTANCEHRID != identifierTypeEnum && tenantIds.size() > 1) {
throw new BulkEditException(DUPLICATES_ACROSS_TENANTS);
}
tenantIds.forEach(tenantId -> {
try (var context = new FolioExecutionContextSetter(refreshAndGetFolioExecutionContext(tenantId, folioExecutionContext))) {
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 {
throw e;
}
}
});
return extendedHoldingsRecordCollection;
} else {
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
} else {
// Process local tenant case
var holdingsRecordCollection = getHoldingsRecordCollection(type, itemIdentifier);
return new ExtendedHoldingsRecordCollection().extendedHoldingsRecords(holdingsRecordCollection.getHoldingsRecords().stream()
.map(holdingsRecord -> new ExtendedHoldingsRecord().tenantId(folioExecutionContext.getTenantId()).entity(holdingsRecord)).toList())
.totalRecords(holdingsRecordCollection.getTotalRecords());
}
}

private HoldingsRecordCollection getHoldingsRecordCollection(IdentifierType type, ItemIdentifier itemIdentifier) {
if (ID == type || HRID == type) {
return checkDuplicates(holdingClient.getHoldingsByQuery(
format(getMatchPattern(identifierType), resolveIdentifier(identifierType), itemIdentifier.getItemId())));
} else if (INSTANCE_HRID == type) {
return holdingClient.getHoldingsByQuery("instanceId==" + holdingsReferenceService.getInstanceIdByHrid(itemIdentifier.getItemId()), Integer.MAX_VALUE);
} else if (ITEM_BARCODE == type) {
return holdingClient.getHoldingsByQuery("id==" + holdingsReferenceService.getHoldingsIdByItemBarcode(itemIdentifier.getItemId()), 1);
} else {
throw new BulkEditException(format("Identifier type \"%s\" is not supported", identifierType));
}
}

private HoldingsRecordCollection checkDuplicates(HoldingsRecordCollection holdingsRecordCollection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.folio.dew.error.BulkEditException;
import org.folio.dew.service.InstanceReferenceService;
import org.folio.dew.service.mapper.InstanceMapper;
import org.folio.spring.FolioExecutionContext;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -35,6 +36,7 @@ public class BulkEditInstanceProcessor implements ItemProcessor<ItemIdentifier,
private final InventoryInstancesClient inventoryInstancesClient;
private final InstanceMapper instanceMapper;
private final InstanceReferenceService instanceReferenceService;
private final FolioExecutionContext folioExecutionContext;

@Value("#{jobParameters['identifierType']}")
private String identifierType;
Expand Down Expand Up @@ -67,10 +69,13 @@ public List<InstanceFormat> process(ItemIdentifier itemIdentifier) throws BulkEd
var isbn = ISBN.equals(IdentifierType.fromValue(identifierType)) ? itemIdentifier.getItemId() : null;
var issn = ISSN.equals(IdentifierType.fromValue(identifierType)) ? itemIdentifier.getItemId() : null;

var tenantId = folioExecutionContext.getTenantId();

return distinctInstances.stream()
.map(r -> instanceMapper.mapToInstanceFormat(r, itemIdentifier.getItemId(), jobId, FilenameUtils.getName(fileName)).withOriginal(r))
.map(instanceFormat -> instanceFormat.withIsbn(isbn))
.map(instanceFormat -> instanceFormat.withIssn(issn))
.map(instanceFormat -> instanceFormat.withTenantId(tenantId))
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

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;
Expand All @@ -12,23 +18,22 @@
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
@StepScope
@RequiredArgsConstructor
@Log4j2
public class BulkEditItemListProcessor implements ItemProcessor<ItemCollection, List<ItemFormat>> {
public class BulkEditItemListProcessor implements ItemProcessor<ExtendedItemCollection, List<ItemFormat>> {
private final BulkEditItemProcessor bulkEditItemProcessor;

@Override
public List<ItemFormat> process(ItemCollection items) {
if (items.getItems().isEmpty()) {
public List<ItemFormat> process(ExtendedItemCollection extendedItemCollection) {
if (extendedItemCollection.getExtendedItems().isEmpty()) {
log.error(NO_MATCH_FOUND_MESSAGE);
throw new BulkEditException(NO_MATCH_FOUND_MESSAGE);
}
return items.getItems().stream()
return extendedItemCollection.getExtendedItems().stream()
.map(bulkEditItemProcessor::process)
.collect(Collectors.toList());
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.folio.dew.domain.dto.ContributorName;
import org.folio.dew.domain.dto.EffectiveCallNumberComponents;
import org.folio.dew.domain.dto.ErrorServiceArgs;
import org.folio.dew.domain.dto.ExtendedItem;
import org.folio.dew.domain.dto.IdentifierType;
import org.folio.dew.domain.dto.Item;
import org.folio.dew.domain.dto.ItemFormat;
Expand All @@ -49,7 +50,7 @@
@StepScope
@RequiredArgsConstructor
@Log4j2
public class BulkEditItemProcessor implements ItemProcessor<Item, ItemFormat> {
public class BulkEditItemProcessor implements ItemProcessor<ExtendedItem, ItemFormat> {
private static final String IS_ACTIVE = "isActive";
private static final String NAME = "name";

Expand All @@ -67,23 +68,25 @@ public class BulkEditItemProcessor implements ItemProcessor<Item, ItemFormat> {
private String fileName;

@Override
public ItemFormat process(Item item) {
public ItemFormat process(ExtendedItem extendedItem) {
var item = extendedItem.getEntity();
var tenantId = extendedItem.getTenantId();
var errorServiceArgs = new ErrorServiceArgs(jobId, getIdentifier(item, identifierType), FilenameUtils.getName(fileName));
var itemFormat = ItemFormat.builder()
.id(item.getId())
.hrid(item.getHrid())
.holdingsRecordId(item.getHoldingsRecordId())
.formerIds(isEmpty(item.getFormerIds()) ? EMPTY : String.join(ARRAY_DELIMITER, escaper.escape(item.getFormerIds())))
.discoverySuppress(booleanToStringNullSafe(item.getDiscoverySuppress()))
.title(getInstanceTitle(item))
.holdingsData(getHoldingsName(item.getHoldingsRecordId()))
.title(getInstanceTitle(item, tenantId))
.holdingsData(getHoldingsName(item.getHoldingsRecordId(), tenantId))
.barcode(item.getBarcode())
.effectiveShelvingOrder(item.getEffectiveShelvingOrder())
.accessionNumber(item.getAccessionNumber())
.itemLevelCallNumber(item.getItemLevelCallNumber())
.itemLevelCallNumberPrefix(item.getItemLevelCallNumberPrefix())
.itemLevelCallNumberSuffix(item.getItemLevelCallNumberSuffix())
.itemLevelCallNumberType(itemReferenceService.getCallNumberTypeNameById(item.getItemLevelCallNumberTypeId(), errorServiceArgs))
.itemLevelCallNumberType(itemReferenceService.getCallNumberTypeNameById(item.getItemLevelCallNumberTypeId(), errorServiceArgs, tenantId))
.effectiveCallNumberComponents(effectiveCallNumberComponentsToString(item.getEffectiveCallNumberComponents()))
.volume(item.getVolume())
.enumeration(item.getEnumeration())
Expand All @@ -96,7 +99,7 @@ public ItemFormat process(Item item) {
.numberOfMissingPieces(item.getNumberOfMissingPieces())
.missingPieces(item.getMissingPieces())
.missingPiecesDate(item.getMissingPiecesDate())
.itemDamagedStatus(itemReferenceService.getDamagedStatusNameById(item.getItemDamagedStatusId(), errorServiceArgs))
.itemDamagedStatus(itemReferenceService.getDamagedStatusNameById(item.getItemDamagedStatusId(), errorServiceArgs, tenantId))
.itemDamagedStatusDate(item.getItemDamagedStatusDate())
.administrativeNotes(isEmpty(item.getAdministrativeNotes()) ? EMPTY : String.join(ARRAY_DELIMITER, escaper.escape(item.getAdministrativeNotes())))
.notes(fetchNotes(item, errorServiceArgs))
Expand All @@ -114,14 +117,16 @@ public ItemFormat process(Item item) {
.statisticalCodes(fetchStatisticalCodes(item, errorServiceArgs))
.tags(isEmpty(item.getTags()) ? EMPTY : String.join(ARRAY_DELIMITER, escaper.escape(item.getTags().getTagList())))
.build();
itemFormat.setElectronicAccess(electronicAccessService.getElectronicAccessesToString(item.getElectronicAccess(), errorServiceArgs));
return itemFormat.withOriginal(item);
itemFormat.setElectronicAccess(electronicAccessService.getElectronicAccessesToString(item.getElectronicAccess(), errorServiceArgs, tenantId));
return itemFormat.withOriginal(item).withTenantId(tenantId);
}

private String getInstanceTitle(Item item) {
JsonNode holding = holdingService.getHoldingById(item.getHoldingsRecordId());
String instanceId = holdingService.getInstanceIdByHolding(holding);
return holdingsReferenceService.getInstanceTitleById(instanceId);
private String getInstanceTitle(Item item, String tenantId) {
var holding = holdingsReferenceService.getHoldingById(item.getHoldingsRecordId(), tenantId);
if (nonNull(holding)) {
return holdingsReferenceService.getInstanceTitleById(holding.getInstanceId(), tenantId);
}
return EMPTY;
}

private String statusToString(Item item) {
Expand Down Expand Up @@ -205,13 +210,13 @@ private String fetchStatisticalCodes(Item item, ErrorServiceArgs args) {
.collect(Collectors.joining(ARRAY_DELIMITER));
}

private String lastCheckInToString(Item item, ErrorServiceArgs args) {
private String lastCheckInToString(Item item, ErrorServiceArgs args, String tenantId) {
var lastCheckIn = item.getLastCheckIn();
if (isEmpty(lastCheckIn)) {
return EMPTY;
}
return String.join(ARRAY_DELIMITER,
escaper.escape(itemReferenceService.getServicePointNameById(lastCheckIn.getServicePointId(), args)),
escaper.escape(itemReferenceService.getServicePointNameById(lastCheckIn.getServicePointId(), args, tenantId)),
escaper.escape(itemReferenceService.getUserNameById(lastCheckIn.getStaffMemberId(), args)),
lastCheckIn.getDateTime());
}
Expand All @@ -231,14 +236,14 @@ private String getIdentifier(Item item, String identifierType) {
}
}

private String getHoldingsName(String holdingsId) {
private String getHoldingsName(String holdingsId, String tenantId) {
if (isEmpty(holdingsId)) {
return EMPTY;
}
var holdingsJson = holdingsReferenceService.getHoldingsJsonById(holdingsId);
var holdingsJson = holdingsReferenceService.getHoldingsJsonById(holdingsId, tenantId);
var locationId = isNull(holdingsJson.get(PERMANENT_LOCATION_ID)) ? null : holdingsJson.get(PERMANENT_LOCATION_ID).asText();

var locationJson = holdingsReferenceService.getHoldingsLocationById(locationId);
var locationJson = holdingsReferenceService.getHoldingsLocationById(locationId, tenantId);
var activePrefix = nonNull(locationJson.get(IS_ACTIVE)) && locationJson.get(IS_ACTIVE).asBoolean() ? EMPTY : "Inactive ";
var name = isNull(locationJson.get(NAME)) ? EMPTY : locationJson.get(NAME).asText();
var locationName = activePrefix + name;
Expand Down
Loading

0 comments on commit e96c2db

Please sign in to comment.