diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 3fb4d7e2..ed5d4da1 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -184,7 +184,12 @@ "usergroups.collection.get", "usergroups.item.get", "users.collection.get", - "users.item.get" + "users.item.get", + "inventory-storage.instance-statuses.item.get", + "inventory-storage.modes-of-issuance.item.get", + "inventory-storage.instance-types.item.get", + "inventory-storage.nature-of-content-terms.item.get", + "inventory-storage.instance-formats.item.get" ] }, { diff --git a/folio-export-common b/folio-export-common index e3fe67a1..1a544213 160000 --- a/folio-export-common +++ b/folio-export-common @@ -1 +1 @@ -Subproject commit e3fe67a1dd9fe0aba1e95b9b326791d051b98ecd +Subproject commit 1a5442138f925b91b49efcfe64bf782f667aa31f diff --git a/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceListProcessor.java b/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceListProcessor.java new file mode 100644 index 00000000..844c799d --- /dev/null +++ b/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceListProcessor.java @@ -0,0 +1,34 @@ +package org.folio.dew.batch.bulkedit.jobs; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.dew.domain.dto.InstanceCollection; +import org.folio.dew.domain.dto.InstanceFormat; +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; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.folio.dew.utils.Constants.NO_MATCH_FOUND_MESSAGE; + +@Component +@StepScope +@RequiredArgsConstructor +@Log4j2 +public class BulkEditInstanceListProcessor implements ItemProcessor> { + private final BulkEditInstanceProcessor bulkEditInstanceProcessor; + + @Override + public List process(InstanceCollection instances) { + if (instances.getInstances().isEmpty()) { + log.error(NO_MATCH_FOUND_MESSAGE); + throw new BulkEditException(NO_MATCH_FOUND_MESSAGE); + } + return instances.getInstances().stream() + .map(bulkEditInstanceProcessor::process) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceProcessor.java b/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceProcessor.java new file mode 100644 index 00000000..9dca6a3f --- /dev/null +++ b/src/main/java/org/folio/dew/batch/bulkedit/jobs/BulkEditInstanceProcessor.java @@ -0,0 +1,120 @@ +package org.folio.dew.batch.bulkedit.jobs; + +import static org.apache.commons.lang3.ObjectUtils.isEmpty; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.io.FilenameUtils; +import org.folio.dew.domain.dto.*; +import org.folio.dew.domain.dto.FormatOfInstance; +import org.folio.dew.domain.dto.Instance; +import org.folio.dew.domain.dto.InstanceContributorsInner; +import org.folio.dew.domain.dto.InstanceSeriesInner; +import org.folio.dew.service.InstanceReferenceService; +import org.folio.dew.service.SpecialCharacterEscaper; +import org.jetbrains.annotations.NotNull; +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.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.folio.dew.utils.Constants.ITEM_DELIMITER; + +@Component +@StepScope +@RequiredArgsConstructor +@Log4j2 +public class BulkEditInstanceProcessor implements ItemProcessor { + + private final InstanceReferenceService instanceReferenceService; + private final SpecialCharacterEscaper escaper; + + + @Value("#{jobParameters['identifierType']}") + private String identifierType; + @Value("#{jobParameters['jobId']}") + private String jobId; + @Value("#{jobParameters['fileName']}") + private String fileName; + + @Override + public InstanceFormat process(@NotNull Instance instance) { + var errorServiceArgs = new ErrorServiceArgs(jobId, getIdentifier(instance, identifierType), FilenameUtils.getName(fileName)); + + var instanceFormat = InstanceFormat.builder() + .id(instance.getId()) + .discoverySuppress(isEmpty(instance.getVersion()) ? EMPTY : Boolean.toString(instance.getDiscoverySuppress())) + .staffSuppress(isEmpty(instance.getStaffSuppress()) ? EMPTY : Boolean.toString(instance.getStaffSuppress())) + .previouslyHeld(isEmpty(instance.getPreviouslyHeld()) ? EMPTY : Boolean.toString(instance.getPreviouslyHeld())) + .hrid(instance.getHrid()) + .source(instance.getSource()) + .catalogedDate(instance.getCatalogedDate()) + .statusId(instanceReferenceService.getInstanceStatusNameById(instance.getStatusId(), errorServiceArgs)) + .modeOfIssuanceId(instanceReferenceService.getModeOfIssuanceNameById(instance.getModeOfIssuanceId(), errorServiceArgs)) + .administrativeNotes(isEmpty(instance.getAdministrativeNotes()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(instance.getAdministrativeNotes()))) + .title(instance.getTitle()) + .indexTitle(instance.getIndexTitle()) + .series(fetchSeries(instance.getSeries())) + .contributors(fetchContributorNames(instance.getContributors())) + .editions(isEmpty(instance.getEditions()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(new ArrayList<>(instance.getEditions())))) + .physicalDescriptions(isEmpty(instance.getPhysicalDescriptions()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(instance.getPhysicalDescriptions()))) + .instanceTypeId(instanceReferenceService.getInstanceTypeNameById(instance.getInstanceTypeId(), errorServiceArgs)) + .natureOfContentTermIds(fetchNatureOfContentTerms(instance.getNatureOfContentTermIds(), errorServiceArgs)) + .instanceFormatIds(fetchInstanceFormats(instance.getInstanceFormats(), errorServiceArgs)) + .languages(isEmpty(instance.getLanguages()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(instance.getLanguages()))) + .publicationFrequency(isEmpty(instance.getPublicationFrequency()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(new ArrayList<>(instance.getPublicationFrequency())))) + .publicationRange(isEmpty(instance.getPublicationRange()) ? EMPTY : String.join(ITEM_DELIMITER, escaper.escape(new ArrayList<>(instance.getPublicationRange())))) + .build(); + + + return instanceFormat.withOriginal(instance); + } + + private String fetchInstanceFormats(List instanceFormats, ErrorServiceArgs errorServiceArgs) { + return isEmpty(instanceFormats) ? EMPTY : + instanceFormats.stream() + .map(iFormat -> instanceReferenceService.getFormatOfInstanceNameById(iFormat.getId(), errorServiceArgs)) + .map(iFormatName -> String.join(ITEM_DELIMITER, escaper.escape(iFormatName))) + .collect(Collectors.joining(ITEM_DELIMITER)); + } + + private String fetchNatureOfContentTerms(Set natureOfContentTermIds, ErrorServiceArgs errorServiceArgs) { + return isEmpty(natureOfContentTermIds) ? EMPTY : + natureOfContentTermIds.stream() + .map(natId -> instanceReferenceService.getNatureOfContentTermNameById(natId, errorServiceArgs)) + .map(natName -> String.join(ITEM_DELIMITER, escaper.escape(natName))) + .collect(Collectors.joining(ITEM_DELIMITER)); + } + + private String fetchContributorNames(List contributors) { + return isEmpty(contributors) ? EMPTY : + contributors.stream() + .map(c -> String.join(ITEM_DELIMITER, escaper.escape(c.getName()))) + .collect(Collectors.joining(ITEM_DELIMITER)); + } + + private String fetchSeries(Set series) { + return isEmpty(series) ? EMPTY : + series.stream() + .map(instanceSeriesInner -> String.join(ITEM_DELIMITER, escaper.escape(instanceSeriesInner.getValue()))) + .collect(Collectors.joining(ITEM_DELIMITER)); + } + + + private String getIdentifier(Instance instance, String identifierType) { + try { + return switch (org.folio.dew.domain.dto.IdentifierType.fromValue(identifierType)) { + case HRID -> instance.getHrid(); + default -> instance.getId(); + }; + } catch (IllegalArgumentException e) { + return instance.getId(); + } + } +} diff --git a/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/BulkEditInstanceIdentifiersJobConfig.java b/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/BulkEditInstanceIdentifiersJobConfig.java new file mode 100644 index 00000000..653b71e4 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/BulkEditInstanceIdentifiersJobConfig.java @@ -0,0 +1,91 @@ +package org.folio.dew.batch.bulkedit.jobs.processidentifiers; + + +import lombok.RequiredArgsConstructor; +import org.folio.dew.batch.CsvListFileWriter; +import org.folio.dew.batch.JsonListFileWriter; +import org.folio.dew.batch.JobCompletionNotificationListener; +import org.folio.dew.batch.bulkedit.jobs.BulkEditInstanceListProcessor; +import org.folio.dew.domain.dto.ExportType; +import org.folio.dew.domain.dto.ItemIdentifier; +import org.folio.dew.domain.dto.InstanceFormat; +import org.folio.dew.error.BulkEditException; +import org.folio.dew.error.BulkEditSkipListener; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.support.CompositeItemProcessor; +import org.springframework.batch.item.support.CompositeItemWriter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.transaction.PlatformTransactionManager; + +import java.util.Arrays; +import java.util.List; + +import static org.folio.dew.domain.dto.EntityType.INSTANCE; +import static org.folio.dew.domain.dto.JobParameterNames.TEMP_LOCAL_FILE_PATH; +import static org.folio.dew.utils.Constants.CHUNKS; +import static org.folio.dew.utils.Constants.JOB_NAME_POSTFIX_SEPARATOR; + +@Configuration +@RequiredArgsConstructor +public class BulkEditInstanceIdentifiersJobConfig { + + private final BulkEditInstanceListProcessor bulkEditInstanceListProcessor; + private final InstanceFetcher instanceFetcher; + private final BulkEditSkipListener bulkEditSkipListener; + + @Bean + public Job bulkEditProcessInstanceIdentifiersJob(JobCompletionNotificationListener listener, Step bulkEditInstanceStep, + JobRepository jobRepository) { + return new JobBuilder(ExportType.BULK_EDIT_IDENTIFIERS + JOB_NAME_POSTFIX_SEPARATOR + INSTANCE.getValue(), jobRepository) + .incrementer(new RunIdIncrementer()) + .listener(listener) + .flow(bulkEditInstanceStep) + .end() + .build(); + } + + @Bean + public Step bulkEditInstanceStep(FlatFileItemReader csvItemIdentifierReader, + CompositeItemWriter> compositeInstanceListWriter, + ListIdentifiersWriteListener listIdentifiersWriteListener, JobRepository jobRepository, + PlatformTransactionManager transactionManager) { + return new StepBuilder("bulkEditInstanceStep", jobRepository) + .> chunk(CHUNKS, transactionManager) + .reader(csvItemIdentifierReader) + .processor(identifierInstanceProcessor()) + .faultTolerant() + .skipLimit(1_000_000) + .processorNonTransactional() // Required to avoid repeating BulkEditItemProcessor#process after skip. + .skip(BulkEditException.class) + .listener(bulkEditSkipListener) + .writer(compositeInstanceListWriter) + .listener(listIdentifiersWriteListener) + .build(); + } + + @Bean + public CompositeItemProcessor> identifierInstanceProcessor() { + var processor = new CompositeItemProcessor>(); + processor.setDelegates(Arrays.asList(instanceFetcher, bulkEditInstanceListProcessor)); + return processor; + } + + @Bean + @StepScope + public CompositeItemWriter> compositeInstanceListWriter(@Value("#{jobParameters['" + TEMP_LOCAL_FILE_PATH + "']}") String outputFileName) { + var writer = new CompositeItemWriter>(); + writer.setDelegates(Arrays.asList(new CsvListFileWriter<>(outputFileName, InstanceFormat.getInstanceColumnHeaders(), InstanceFormat.getInstanceFieldsArray(), (field, i) -> field), + new JsonListFileWriter<>(new FileSystemResource(outputFileName + ".json")))); + return writer; + } +} diff --git a/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/InstanceFetcher.java b/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/InstanceFetcher.java new file mode 100644 index 00000000..651879f4 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/bulkedit/jobs/processidentifiers/InstanceFetcher.java @@ -0,0 +1,52 @@ +package org.folio.dew.batch.bulkedit.jobs.processidentifiers; + +import feign.codec.DecodeException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.dew.client.InventoryInstancesClient; +import org.folio.dew.domain.dto.IdentifierType; +import org.folio.dew.domain.dto.InstanceCollection; +import org.folio.dew.domain.dto.ItemIdentifier; +import org.folio.dew.error.BulkEditException; +import org.folio.dew.utils.ExceptionHelper; +import org.jetbrains.annotations.NotNull; +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.HashSet; +import java.util.Set; + +import static org.folio.dew.domain.dto.IdentifierType.HOLDINGS_RECORD_ID; +import static org.folio.dew.utils.BulkEditProcessorHelper.getMatchPattern; +import static org.folio.dew.utils.BulkEditProcessorHelper.resolveIdentifier; + +@Component +@StepScope +@RequiredArgsConstructor +@Log4j2 +public class InstanceFetcher implements ItemProcessor { + private final InventoryInstancesClient inventoryInstancesClient; + + @Value("#{jobParameters['identifierType']}") + private String identifierType; + + private final Set identifiersToCheckDuplication = new HashSet<>(); + + @Override + public InstanceCollection process(@NotNull ItemIdentifier itemIdentifier) throws BulkEditException { + if (identifiersToCheckDuplication.contains(itemIdentifier)) { + throw new BulkEditException("Duplicate entry"); + } + identifiersToCheckDuplication.add(itemIdentifier); + var limit = HOLDINGS_RECORD_ID == IdentifierType.fromValue(identifierType) ? Integer.MAX_VALUE : 1; + var idType = resolveIdentifier(identifierType); + var identifier = "barcode".equals(idType) ? Utils.encode(itemIdentifier.getItemId()) : itemIdentifier.getItemId(); + try { + return inventoryInstancesClient.getInstanceByQuery(String.format(getMatchPattern(identifierType), idType, identifier), limit); + } catch (DecodeException e) { + throw new BulkEditException(ExceptionHelper.fetchMessage(e)); + } + } +} diff --git a/src/main/java/org/folio/dew/client/InstanceFormatsClient.java b/src/main/java/org/folio/dew/client/InstanceFormatsClient.java new file mode 100644 index 00000000..b34be0fb --- /dev/null +++ b/src/main/java/org/folio/dew/client/InstanceFormatsClient.java @@ -0,0 +1,14 @@ +package org.folio.dew.client; + +import org.folio.dew.config.feign.FeignClientConfiguration; +import org.folio.dew.domain.dto.FormatOfInstance; +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; + +@FeignClient(name = "instance-formats", configuration = FeignClientConfiguration.class) +public interface InstanceFormatsClient { + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + FormatOfInstance getById(@PathVariable String id); +} diff --git a/src/main/java/org/folio/dew/client/InstanceModeOfIssuanceClient.java b/src/main/java/org/folio/dew/client/InstanceModeOfIssuanceClient.java new file mode 100644 index 00000000..483fed14 --- /dev/null +++ b/src/main/java/org/folio/dew/client/InstanceModeOfIssuanceClient.java @@ -0,0 +1,14 @@ +package org.folio.dew.client; + +import org.folio.dew.config.feign.FeignClientConfiguration; +import org.folio.dew.domain.dto.IssuanceMode; +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; + +@FeignClient(name = "modes-of-issuance", configuration = FeignClientConfiguration.class) +public interface InstanceModeOfIssuanceClient { + @GetMapping(value = "/{modeOfIssuanceId}", produces = MediaType.APPLICATION_JSON_VALUE) + IssuanceMode getById(@PathVariable String modeOfIssuanceId); +} diff --git a/src/main/java/org/folio/dew/client/InstanceStatusesClient.java b/src/main/java/org/folio/dew/client/InstanceStatusesClient.java new file mode 100644 index 00000000..877fa19b --- /dev/null +++ b/src/main/java/org/folio/dew/client/InstanceStatusesClient.java @@ -0,0 +1,14 @@ +package org.folio.dew.client; + +import org.folio.dew.config.feign.FeignClientConfiguration; +import org.folio.dew.domain.dto.InstanceStatus; +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; + +@FeignClient(name = "instance-statuses", configuration = FeignClientConfiguration.class) +public interface InstanceStatusesClient { + @GetMapping(value = "/{instanceStatusId}", produces = MediaType.APPLICATION_JSON_VALUE) + InstanceStatus getById(@PathVariable String instanceStatusId); +} diff --git a/src/main/java/org/folio/dew/client/InstanceTypesClient.java b/src/main/java/org/folio/dew/client/InstanceTypesClient.java new file mode 100644 index 00000000..ee81942e --- /dev/null +++ b/src/main/java/org/folio/dew/client/InstanceTypesClient.java @@ -0,0 +1,13 @@ +package org.folio.dew.client; + +import org.folio.dew.config.feign.FeignClientConfiguration; +import org.folio.dew.domain.dto.InstanceType; +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; +@FeignClient(name = "instance-types", configuration = FeignClientConfiguration.class) +public interface InstanceTypesClient { + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + InstanceType getById(@PathVariable String id); +} diff --git a/src/main/java/org/folio/dew/client/InventoryInstancesClient.java b/src/main/java/org/folio/dew/client/InventoryInstancesClient.java new file mode 100644 index 00000000..4a097da8 --- /dev/null +++ b/src/main/java/org/folio/dew/client/InventoryInstancesClient.java @@ -0,0 +1,30 @@ +package org.folio.dew.client; + +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import org.folio.dew.config.feign.FeignEncoderConfiguration; +import org.folio.dew.domain.dto.Instance; +import org.folio.dew.domain.dto.InstanceCollection; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "inventory/instances", configuration = FeignEncoderConfiguration.class) +public interface InventoryInstancesClient { + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + InstanceCollection getInstanceByQuery(@RequestParam String query); + + @GetMapping(value = "/{instanceId}", produces = MediaType.APPLICATION_JSON_VALUE) + Instance getInstanceById(@PathVariable String instanceId); + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + InstanceCollection getInstanceByQuery(@RequestParam("query") String query, @RequestParam long limit); + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + InstanceCollection getInstanceByQuery(@RequestParam("query") String query, @RequestParam long offset, @RequestParam long limit); + + @PutMapping(value = "/{instanceId}", consumes = MediaType.APPLICATION_JSON_VALUE) + void updateInstance(@RequestBody Instance instance, @PathVariable String instanceId); +} diff --git a/src/main/java/org/folio/dew/client/NatureOfContentTermsClient.java b/src/main/java/org/folio/dew/client/NatureOfContentTermsClient.java new file mode 100644 index 00000000..28e6d972 --- /dev/null +++ b/src/main/java/org/folio/dew/client/NatureOfContentTermsClient.java @@ -0,0 +1,14 @@ +package org.folio.dew.client; + +import org.folio.dew.config.feign.FeignClientConfiguration; +import org.folio.dew.domain.dto.NatureOfContentTerm; +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; + +@FeignClient(name = "nature-of-content-terms", configuration = FeignClientConfiguration.class) +public interface NatureOfContentTermsClient { + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + NatureOfContentTerm getById(@PathVariable String id); +} diff --git a/src/main/java/org/folio/dew/controller/BulkEditController.java b/src/main/java/org/folio/dew/controller/BulkEditController.java index fe799da5..b16e0fd3 100644 --- a/src/main/java/org/folio/dew/controller/BulkEditController.java +++ b/src/main/java/org/folio/dew/controller/BulkEditController.java @@ -8,6 +8,7 @@ import static org.folio.dew.domain.dto.EntityType.HOLDINGS_RECORD; import static org.folio.dew.domain.dto.EntityType.ITEM; import static org.folio.dew.domain.dto.EntityType.USER; +import static org.folio.dew.domain.dto.EntityType.INSTANCE; import static org.folio.dew.domain.dto.ExportType.BULK_EDIT_IDENTIFIERS; import static org.folio.dew.domain.dto.ExportType.BULK_EDIT_UPDATE; import static org.folio.dew.domain.dto.JobParameterNames.PREVIEW_FILE_NAME; @@ -65,6 +66,7 @@ import org.folio.dew.domain.dto.HoldingsFormat; import org.folio.dew.domain.dto.HoldingsRecordCollection; import org.folio.dew.domain.dto.IdentifierType; +import org.folio.dew.domain.dto.InstanceFormat; import org.folio.dew.domain.dto.ItemCollection; import org.folio.dew.domain.dto.ItemContentUpdateCollection; import org.folio.dew.domain.dto.ItemFormat; @@ -569,6 +571,8 @@ private int getIdentifierIndex(JobCommand jobCommand) { return Arrays.asList(ItemFormat.getItemFieldsArray()).indexOf(resolveIdentifier(jobCommand.getIdentifierType().getValue())); } else if (HOLDINGS_RECORD == jobCommand.getEntityType()) { return Arrays.asList(HoldingsFormat.getHoldingsFieldsArray()).indexOf(IdentifierType.ID.getValue().toLowerCase()); + }else if (INSTANCE == jobCommand.getEntityType()) { + return Arrays.asList(InstanceFormat.getInstanceFieldsArray()).indexOf(IdentifierType.ID.getValue().toLowerCase()); } else { throw new NonSupportedEntityException(format("Non-supported entity type: %s", jobCommand.getEntityType())); } diff --git a/src/main/java/org/folio/dew/domain/dto/InstanceFormat.java b/src/main/java/org/folio/dew/domain/dto/InstanceFormat.java new file mode 100644 index 00000000..8eec668e --- /dev/null +++ b/src/main/java/org/folio/dew/domain/dto/InstanceFormat.java @@ -0,0 +1,124 @@ +package org.folio.dew.domain.dto; + + +import com.opencsv.bean.CsvBindByName; +import com.opencsv.bean.CsvBindByPosition; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.With; +import org.apache.commons.lang3.reflect.FieldUtils; + +import java.lang.reflect.Field; +import java.util.stream.Collectors; +@Data +@Builder +@With +@NoArgsConstructor +@AllArgsConstructor +public class InstanceFormat implements Formatable { + + private org.folio.dew.domain.dto.Instance original; + + @CsvBindByName(column = "Instance UUID") + @CsvBindByPosition(position = 0) + private String id; + + @CsvBindByName(column = "Suppress from discovery") + @CsvBindByPosition(position = 1) + private String discoverySuppress; + + @CsvBindByName(column = "Staff suppress") + @CsvBindByPosition(position = 2) + private String staffSuppress; + + @CsvBindByName(column = "Previously held") + @CsvBindByPosition(position = 3) + private String previouslyHeld; + + @CsvBindByName(column = "Instance HRID") + @CsvBindByPosition(position = 4) + private String hrid; + + @CsvBindByName(column = "Source") + @CsvBindByPosition(position = 5) + private String source; + + @CsvBindByName(column = "Cataloged date") + @CsvBindByPosition(position = 6) + private String catalogedDate; + + @CsvBindByName(column = "Instance status term") + @CsvBindByPosition(position = 7) + private String statusId; + + @CsvBindByName(column = "Mode of issuance") + @CsvBindByPosition(position = 8) + private String modeOfIssuanceId; + + @CsvBindByName(column = "Administrative note") + @CsvBindByPosition(position = 9) + private String administrativeNotes; + + @CsvBindByName(column = "Resource title") + @CsvBindByPosition(position = 10) + private String title; + + @CsvBindByName(column = "Index title") + @CsvBindByPosition(position = 11) + private String indexTitle; + + @CsvBindByName(column = "Series statements") + @CsvBindByPosition(position = 12) + private String series; + + @CsvBindByName(column = "Contributors") + @CsvBindByPosition(position = 13) + private String contributors; + + @CsvBindByName(column = "Edition") + @CsvBindByPosition(position = 14) + private String editions; + + @CsvBindByName(column = "Physical description") + @CsvBindByPosition(position = 15) + private String physicalDescriptions; + + @CsvBindByName(column = "Resource type") + @CsvBindByPosition(position = 16) + private String instanceTypeId; + + @CsvBindByName(column = "Nature of content") + @CsvBindByPosition(position = 17) + private String natureOfContentTermIds; + + @CsvBindByName(column = "Formats") + @CsvBindByPosition(position = 18) + private String instanceFormatIds; + + @CsvBindByName(column = "Languages") + @CsvBindByPosition(position = 19) + private String languages; + + @CsvBindByName(column = "Publication frequency") + @CsvBindByPosition(position = 20) + private String publicationFrequency; + + @CsvBindByName(column = "Publication range") + @CsvBindByPosition(position = 21) + private String publicationRange; + + public static String[] getInstanceFieldsArray() { + return FieldUtils.getFieldsListWithAnnotation(InstanceFormat.class, CsvBindByName.class).stream() + .map(Field::getName) + .toArray(String[]::new); + } + + public static String getInstanceColumnHeaders() { + return FieldUtils.getFieldsListWithAnnotation(InstanceFormat.class, CsvBindByName.class).stream() + .map(field -> field.getAnnotation(CsvBindByName.class).column()) + .collect(Collectors.joining(",")); + } + +} diff --git a/src/main/java/org/folio/dew/service/InstanceReferenceService.java b/src/main/java/org/folio/dew/service/InstanceReferenceService.java new file mode 100644 index 00000000..91e14d2e --- /dev/null +++ b/src/main/java/org/folio/dew/service/InstanceReferenceService.java @@ -0,0 +1,75 @@ +package org.folio.dew.service; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.dew.client.*; +import org.folio.dew.domain.dto.ErrorServiceArgs; +import org.folio.dew.error.BulkEditException; +import org.folio.dew.error.NotFoundException; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import static org.apache.commons.lang3.ObjectUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +@Service +@Log4j2 +@RequiredArgsConstructor +public class InstanceReferenceService { + + private final BulkEditProcessingErrorsService errorsService; + private final InstanceStatusesClient instanceStatusesClient; + private final InstanceModeOfIssuanceClient instanceModeOfIssuanceClient; + private final InstanceTypesClient instanceTypesClient; + private final NatureOfContentTermsClient natureOfContentTermsClient; + private final InstanceFormatsClient instanceFormatsClient; + + + @Cacheable(cacheNames = "instanceStatusNames") + public String getInstanceStatusNameById(String instanceStatusId, ErrorServiceArgs args) { + try { + return isEmpty(instanceStatusId) ? EMPTY : instanceStatusesClient.getById(instanceStatusId).getName(); + } catch (NotFoundException e) { + errorsService.saveErrorInCSV(args.getJobId(), args.getIdentifier(), new BulkEditException(String.format("Instance status was not found by id: [%s]", instanceStatusId)), args.getFileName()); + return instanceStatusId; + } + } + @Cacheable(cacheNames = "issuanceModeNames") + public String getModeOfIssuanceNameById(String issuanceModeId, ErrorServiceArgs args) { + try { + return isEmpty(issuanceModeId) ? EMPTY : instanceModeOfIssuanceClient.getById(issuanceModeId).getName(); + } catch (NotFoundException e) { + errorsService.saveErrorInCSV(args.getJobId(), args.getIdentifier(), new BulkEditException(String.format("Issuance mode was not found by id: [%s]", issuanceModeId)), args.getFileName()); + return issuanceModeId; + } + } + @Cacheable(cacheNames = "instanceTypes") + public String getInstanceTypeNameById(String instanceTypeId, ErrorServiceArgs args) { + try { + return isEmpty(instanceTypeId) ? EMPTY : instanceTypesClient.getById(instanceTypeId).getName(); + } catch (NotFoundException e) { + errorsService.saveErrorInCSV(args.getJobId(), args.getIdentifier(), new BulkEditException(String.format("Instance type was not found by id: [%s]", instanceTypeId)), args.getFileName()); + return instanceTypeId; + } + } + @Cacheable(cacheNames = "natureOfContentTermIds") + public String getNatureOfContentTermNameById(String natureOfContentTermId, ErrorServiceArgs args) { + try { + return isEmpty(natureOfContentTermId) ? EMPTY : natureOfContentTermsClient.getById(natureOfContentTermId).getName(); + } catch (NotFoundException e) { + errorsService.saveErrorInCSV(args.getJobId(), args.getIdentifier(), new BulkEditException(String.format("Nature of content term was not found by id: [%s]", natureOfContentTermId)), args.getFileName()); + return natureOfContentTermId; + } + } + @Cacheable(cacheNames = "instanceFormatIds") + public String getFormatOfInstanceNameById(String instanceFormatId, ErrorServiceArgs args) { + try { + return isEmpty(instanceFormatId) ? EMPTY : instanceFormatsClient.getById(instanceFormatId).getName(); + } catch (NotFoundException e) { + errorsService.saveErrorInCSV(args.getJobId(), args.getIdentifier(), new BulkEditException(String.format("Instance format was not found by id: [%s]", instanceFormatId)), args.getFileName()); + return instanceFormatId; + } + } + +} diff --git a/src/main/resources/swagger.api/bulk-edit.yaml b/src/main/resources/swagger.api/bulk-edit.yaml index 4f1a9b19..60f4d53b 100644 --- a/src/main/resources/swagger.api/bulk-edit.yaml +++ b/src/main/resources/swagger.api/bulk-edit.yaml @@ -659,6 +659,24 @@ components: $ref: '../../../../folio-export-common/schemas/inventory/holdingsRecordsSourceCollection.json#/HoldingsRecordsSourceCollection' holdingsContentUpdateCollection: $ref: '../../../../folio-export-common/schemas/bulk-edit/holdingsContentUpdateCollection.json#/HoldingsContentUpdateCollection' + instance: + $ref: '../../../../folio-export-common/schemas/inventory-storage/instance.json#/Instance' + instanceCollection: + $ref: '../../../../folio-export-common/schemas/inventory-storage/instanceCollection.json#/InstanceCollection' + formatOfInstance: + $ref: '../../../../folio-export-common/schemas/inventory-storage/formatOfInstance.json#/FormatOfInstance' + classificationType: + $ref: '../../../../folio-export-common/schemas/inventory-storage/classificationType.json#/ClassificationType' + contributorNameType: + $ref: '../../../../folio-export-common/schemas/inventory-storage/contributorNameType.json#/ContributorNameType' + instanceStatus: + $ref: '../../../../folio-export-common/schemas/inventory/instanceStatus.json#/InstanceStatus' + issuanceMode: + $ref: '../../../../folio-export-common/schemas/inventory/issuanceMode.json#/IssuanceMode' + instanceType: + $ref: '../../../../folio-export-common/schemas/inventory/instanceType.json#/InstanceType' + natureOfContentTerm: + $ref: '../../../../folio-export-common/schemas/inventory/natureOfContentTerm.json#/NatureOfContentTerm' examples: errors: value: