diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index ffae1b7a4..63b157728 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -203,7 +203,8 @@
"inventory-storage.instance-types.collection.get",
"inventory-storage.nature-of-content-terms.item.get",
"inventory-storage.instance-formats.item.get",
- "inventory-storage.instance-note-types.item.get"
+ "inventory-storage.instance-note-types.item.get",
+ "source-storage.sourceRecords.get"
]
},
{
diff --git a/folio-export-common b/folio-export-common
index a31fe18b8..7439ca7f0 160000
--- a/folio-export-common
+++ b/folio-export-common
@@ -1 +1 @@
-Subproject commit a31fe18b8a62d16ea73fd37f766afa2e35865cba
+Subproject commit 7439ca7f0ba5976eacde0f101052497f0a4ba7c6
diff --git a/pom.xml b/pom.xml
index 6efdec822..4cacd6a3a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,7 @@
3.7.3
5.7.1
12.1
+ 2.9.2
2.4.0
@@ -240,6 +241,12 @@
3.1.0-M1
+
+ org.marc4j
+ marc4j
+ ${marc4j.version}
+
+
diff --git a/src/main/java/org/folio/dew/batch/MarcAsListStringsWriter.java b/src/main/java/org/folio/dew/batch/MarcAsListStringsWriter.java
index f9438a6bb..d17f4d45d 100644
--- a/src/main/java/org/folio/dew/batch/MarcAsListStringsWriter.java
+++ b/src/main/java/org/folio/dew/batch/MarcAsListStringsWriter.java
@@ -3,6 +3,8 @@
import lombok.extern.log4j.Log4j2;
import org.folio.dew.client.SrsClient;
import org.folio.dew.domain.dto.Formatable;
+import org.folio.dew.error.BulkEditException;
+import org.folio.dew.service.JsonToMarcConverter;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ExecutionContext;
@@ -10,10 +12,14 @@
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.util.Assert;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import static java.lang.String.format;
import static java.util.Objects.nonNull;
+import static org.folio.dew.utils.Constants.NO_MARC_CONTENT;
@Log4j2
@StepScope
@@ -21,17 +27,27 @@ public class MarcAsListStringsWriter> extends FlatFil
private SrsClient srsClient;
private MarcAsStringWriter delegateToStringWriter;
+ private JsonToMarcConverter jsonToMarcConverter;
- public MarcAsListStringsWriter(String outputFileName, SrsClient srsClient) {
+ public MarcAsListStringsWriter(String outputFileName, SrsClient srsClient, JsonToMarcConverter jsonToMarcConverter) {
super();
this.srsClient = srsClient;
+ this.jsonToMarcConverter = jsonToMarcConverter;
delegateToStringWriter = new MarcAsStringWriter<>(outputFileName);
}
@Override
public void write(Chunk extends List> items) throws Exception {
- delegateToStringWriter.write(new Chunk<>(items.getItems().stream().flatMap(List::stream).filter(itm -> itm.isInstanceFormat() && itm.isSourceMarc()).map(marc -> getMarcContent(marc.getId()))
- .filter(Objects::nonNull).toList()));
+ delegateToStringWriter.write(new Chunk<>(items.getItems().stream().flatMap(List::stream)
+ .filter(itm -> itm.isInstanceFormat() && itm.isSourceMarc()).map(marc -> {
+ try {
+ return getMarcContent(marc.getId());
+ } catch (Exception e) {
+ log.error(e);
+ throw new BulkEditException(format(NO_MARC_CONTENT, marc.getId(), e.getMessage()));
+ }
+ })
+ .flatMap(List::stream).filter(Objects::nonNull).toList()));
}
@Override
@@ -60,15 +76,19 @@ public void close() {
}
}
- private String getMarcContent(String id) {
- var srsRecords = srsClient.getMarc(id, "INSTANCE");
- if (srsRecords.getSourceRecords().isEmpty()) {
+ private List getMarcContent(String id) throws Exception {
+ List mrcRecords = new ArrayList<>();
+ var srsRecords = srsClient.getMarc(id, "INSTANCE").get("sourceRecords");
+ if (srsRecords.isEmpty()) {
log.warn("No SRS records found by instanceId = {}", id);
- return null;
+ return mrcRecords;
}
- var recordId = srsRecords.getSourceRecords().get(0).getRecordId();
- var marcRecord = srsClient.getMarcContent(recordId);
- log.info("MARC record found by recordId = {}", recordId);
- return marcRecord.getRawRecord().getContent();
+ for (var jsonNodeIterator = srsRecords.elements(); jsonNodeIterator.hasNext();) {
+ var srsRec = jsonNodeIterator.next();
+ var parsedRec = srsRec.get("parsedRecord");
+ var content = parsedRec.get("content").toString();
+ mrcRecords.add(jsonToMarcConverter.convertJsonRecordToMarcRecord(content));
+ }
+ return mrcRecords;
}
}
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
index 136c52da6..986cd5890 100644
--- 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
@@ -13,6 +13,7 @@
import org.folio.dew.domain.dto.ItemIdentifier;
import org.folio.dew.error.BulkEditException;
import org.folio.dew.error.BulkEditSkipListener;
+import org.folio.dew.service.JsonToMarcConverter;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
@@ -43,6 +44,7 @@ public class BulkEditInstanceIdentifiersJobConfig {
private final BulkEditInstanceProcessor bulkEditInstanceProcessor;
private final BulkEditSkipListener bulkEditSkipListener;
private final SrsClient srsClient;
+ private final JsonToMarcConverter jsonToMarcConverter;
@Bean
public Job bulkEditProcessInstanceIdentifiersJob(JobCompletionNotificationListener listener, Step bulkEditInstanceStep,
@@ -80,7 +82,7 @@ public CompositeItemWriter> compositeInstanceListWriter(@Va
@Value("#{jobParameters['" + TEMP_LOCAL_MARC_PATH + "']}") String outputMarcName) {
var writer = new CompositeItemWriter>();
writer.setDelegates(Arrays.asList(new CsvListFileWriter<>(outputFileName, InstanceFormat.getInstanceColumnHeaders(), InstanceFormat.getInstanceFieldsArray(), (field, i) -> field),
- new JsonListFileWriter<>(new FileSystemResource(outputFileName + ".json")), new MarcAsListStringsWriter<>(outputMarcName, srsClient)));
+ new JsonListFileWriter<>(new FileSystemResource(outputFileName + ".json")), new MarcAsListStringsWriter<>(outputMarcName, srsClient, jsonToMarcConverter)));
return writer;
}
}
diff --git a/src/main/java/org/folio/dew/client/SrsClient.java b/src/main/java/org/folio/dew/client/SrsClient.java
index a2cc2f7e7..af54d8347 100644
--- a/src/main/java/org/folio/dew/client/SrsClient.java
+++ b/src/main/java/org/folio/dew/client/SrsClient.java
@@ -1,19 +1,15 @@
package org.folio.dew.client;
+import com.fasterxml.jackson.databind.JsonNode;
import org.folio.dew.config.feign.FeignClientConfiguration;
-import org.folio.dew.domain.dto.MarcRecord;
-import org.folio.dew.domain.dto.SrsRecordCollection;
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 = "source-storage", configuration = FeignClientConfiguration.class)
public interface SrsClient {
- @GetMapping(value = "/source-records")
- SrsRecordCollection getMarc(@RequestParam("instanceId") String instanceId, @RequestParam("idType") String idType);
-
- @GetMapping(value = "/records/{srsId}")
- MarcRecord getMarcContent(@PathVariable String srsId);
+ @GetMapping(value = "/source-records", produces = MediaType.APPLICATION_JSON_VALUE)
+ JsonNode getMarc(@RequestParam("instanceId") String instanceId, @RequestParam("idType") String idType);
}
diff --git a/src/main/java/org/folio/dew/service/JsonToMarcConverter.java b/src/main/java/org/folio/dew/service/JsonToMarcConverter.java
new file mode 100644
index 000000000..05d92fef1
--- /dev/null
+++ b/src/main/java/org/folio/dew/service/JsonToMarcConverter.java
@@ -0,0 +1,43 @@
+package org.folio.dew.service;
+
+import lombok.extern.log4j.Log4j2;
+import org.marc4j.MarcException;
+import org.marc4j.MarcJsonReader;
+import org.marc4j.MarcStreamWriter;
+import org.springframework.stereotype.Component;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@Log4j2
+@Component
+public class JsonToMarcConverter {
+
+ public String convertJsonRecordToMarcRecord(String jsonRecord) throws IOException {
+ var byteArrayInputStream = new ByteArrayInputStream(jsonRecord.getBytes(StandardCharsets.UTF_8));
+ var byteArrayOutputStream = new ByteArrayOutputStream();
+ try (byteArrayInputStream; byteArrayOutputStream) {
+ var marcJsonReader = new MarcJsonReader(byteArrayInputStream);
+ var marcStreamWriter = new MarcStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8.name());
+ writeMarc(marcJsonReader, marcStreamWriter);
+ return byteArrayOutputStream.toString();
+ } catch (IOException e) {
+ log.error(e.getMessage());
+ throw e;
+ }
+ }
+
+ private void writeMarc(MarcJsonReader marcJsonReader, MarcStreamWriter marcStreamWriter) {
+ try {
+ while (marcJsonReader.hasNext()) {
+ var marc = marcJsonReader.next();
+ marcStreamWriter.write(marc);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ throw new MarcException(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/org/folio/dew/utils/Constants.java b/src/main/java/org/folio/dew/utils/Constants.java
index 3b2badf90..34ad3e9c2 100644
--- a/src/main/java/org/folio/dew/utils/Constants.java
+++ b/src/main/java/org/folio/dew/utils/Constants.java
@@ -52,6 +52,7 @@ public class Constants {
public static final String STATUS_FIELD_CAN_NOT_CLEARED = "Status field can not be cleared";
public static final String STATUS_VALUE_NOT_ALLOWED = "New status value \"%s\" is not allowed";
public static final String MULTIPLE_MATCHES_MESSAGE = "Multiple matches for the same identifier.";
+ public static final String NO_MARC_CONTENT = "Cannot get marc content for record with id = %s, reason: %s";
public static final String MODULE_NAME = "BULKEDIT";
public static final String BULKEDIT_DIR_NAME = "bulk_edit";
diff --git a/src/main/resources/swagger.api/bulk-edit.yaml b/src/main/resources/swagger.api/bulk-edit.yaml
index aaad1c060..f295d6e52 100644
--- a/src/main/resources/swagger.api/bulk-edit.yaml
+++ b/src/main/resources/swagger.api/bulk-edit.yaml
@@ -683,14 +683,6 @@ components:
$ref: '../../../../folio-export-common/schemas/inventory/identifierTypeReferenceCollection.json#/IdentifierTypeReferenceCollection'
InstanceNoteType:
$ref: '../../../../folio-export-common/schemas/inventory/instanceNoteType.json#/InstanceNoteType'
- RawRecord:
- $ref: '../../../../folio-export-common/schemas/srs/rawRecord.json#/RawRecord'
- MarcRecord:
- $ref: '../../../../folio-export-common/schemas/srs/marcRecord.json#/MarcRecord'
- SrsRecord:
- $ref: '../../../../folio-export-common/schemas/srs/srsRecord.json#/SrsRecord'
- SrsRecordCollection:
- $ref: '../../../../folio-export-common/schemas/srs/srsRecordCollection.json#/SrsRecordCollection'
examples:
errors:
value:
diff --git a/src/test/java/org/folio/dew/BulkEditTest.java b/src/test/java/org/folio/dew/BulkEditTest.java
index fe361d8e9..a3dc05264 100644
--- a/src/test/java/org/folio/dew/BulkEditTest.java
+++ b/src/test/java/org/folio/dew/BulkEditTest.java
@@ -109,6 +109,7 @@ class BulkEditTest extends BaseBatchTest {
private static final String ITEM_BARCODES_CSV = "src/test/resources/upload/item_barcodes.csv";
private static final String INSTANCE_HRIDS_CSV = "src/test/resources/upload/instance_hrids.csv";
private static final String MARC_INSTANCE_ID_CSV = "src/test/resources/upload/marc_instance_id.csv";
+ private static final String MARC_INSTANCE_ID_INVALID_CONTENT_CSV = "src/test/resources/upload/marc_instance_id_invalid_content.csv";
private static final String MARC_INSTANCE_HRID_CSV = "src/test/resources/upload/marc_instance_hrid.csv";
private static final String INSTANCE_ISSN_ISBN_CSV = "src/test/resources/upload/instance_ISSN_ISBN.csv";
private static final String ITEM_BARCODES_DOUBLE_QOUTES_CSV = "src/test/resources/upload/item_barcodes_double_qoutes.csv";
@@ -324,7 +325,58 @@ void uploadMarcInstanceIdentifiersJobTest(String identifierType, String path) th
final FileSystemResource actualResult = actualFileOutput(jobExecution.getExecutionContext().getString(OUTPUT_FILES_IN_STORAGE).split(";")[3]);
- assertEquals("marc content", new String(actualResult.getContentAsByteArray()));
+ assertEquals("00026nam a2200025 a 4500\u001E\u001D", new String(actualResult.getContentAsByteArray()));
+ assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
+ }
+
+ @Test
+ void uploadMarcInstanceIdentifiersInvalidContentJobTest() throws Exception {
+
+ var path = MARC_INSTANCE_ID_INVALID_CONTENT_CSV;
+ JobLauncherTestUtils testLauncher = createTestLauncher(bulkEditProcessInstanceIdentifiersJob);
+
+ var parametersBuilder = new JobParametersBuilder();
+ String jobId = UUID.randomUUID().toString();
+ String workDir = getWorkingDirectory(springApplicationName, BULKEDIT_DIR_NAME);
+ parametersBuilder.addString(TEMP_OUTPUT_MARC_PATH, workDir + jobId + "/" + "marc_instance_id");
+ parametersBuilder.addString(TEMP_LOCAL_MARC_PATH,
+ getTempDirWithSeparatorSuffix() + springApplicationName + PATH_SEPARATOR + jobId + PATH_SEPARATOR + "marc_instance_id");
+ parametersBuilder.addString(TEMP_LOCAL_FILE_PATH,
+ getTempDirWithSeparatorSuffix() + springApplicationName + PATH_SEPARATOR + jobId + PATH_SEPARATOR + "out");
+ parametersBuilder.addString(TEMP_OUTPUT_FILE_PATH, workDir + jobId + "/" + "out");
+ try {
+ localFilesStorage.write(workDir + "marc_instance_id", new byte[32]);
+ localFilesStorage.write(workDir+ jobId + "/marc_instance_id.mrc", new byte[32]);
+ localFilesStorage.write(workDir + "out", new byte[0]);
+ localFilesStorage.write(workDir + "out.csv", new byte[0]);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ Path of = Path.of(path);
+ var file = getWorkingDirectory("mod-data-export-worker", BULKEDIT_DIR_NAME) +
+ FilenameUtils.removeExtension((new File(path)).getName()) + "E" + FilenameUtils.getExtension(path);
+ parametersBuilder.addString(FILE_NAME, file);
+ localFilesStorage.write(file, Files.readAllBytes(of));
+ parametersBuilder.addLong(TOTAL_CSV_LINES, countLines(localFilesStorage, file, false), false);
+
+ var tempDir = getTempDirWithSeparatorSuffix() + springApplicationName + PATH_SEPARATOR + jobId;
+ var tempFile = tempDir + PATH_SEPARATOR + of.getFileName();
+ Files.createDirectories(Path.of(tempDir));
+ Files.write(Path.of(tempFile), Files.readAllBytes(of));
+ parametersBuilder.addString(TEMP_IDENTIFIERS_FILE_NAME, tempFile);
+
+ parametersBuilder.addString(JobParameterNames.JOB_ID, jobId);
+ parametersBuilder.addString(EXPORT_TYPE, BULK_EDIT_IDENTIFIERS.getValue());
+ parametersBuilder.addString(ENTITY_TYPE, INSTANCE.getValue());
+ parametersBuilder.addString(IDENTIFIER_TYPE, "ID");
+
+ final JobParameters jobParameters = parametersBuilder.toJobParameters();
+
+ JobExecution jobExecution = testLauncher.launchJob(jobParameters);
+
+ final FileSystemResource actualResult = actualFileOutput(jobExecution.getExecutionContext().getString(OUTPUT_FILES_IN_STORAGE).split(";")[3]);
+
+ assertEquals("", new String(actualResult.getContentAsByteArray()).trim());
assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
}
diff --git a/src/test/resources/mappings/instances-query.json b/src/test/resources/mappings/instances-query.json
index e05a63d5e..990dc524f 100644
--- a/src/test/resources/mappings/instances-query.json
+++ b/src/test/resources/mappings/instances-query.json
@@ -221,6 +221,19 @@
}
}
},
+ {
+ "request": {
+ "method": "GET",
+ "url": "/inventory/instances?query=id%3D%3D7772796a-b88b-4991-a9f7-2e368217c487&limit=1"
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"instances\": [\n {\n \"id\": \"7772796a-b88b-4991-a9f7-2e368217c487\",\n \"_version\": \"3\",\n \"hrid\": \"inst000000000022\",\n \"source\": \"MARC\",\n \"title\": \"American Bar Association journal.\",\n \"administrativeNotes\": [],\n \"indexTitle\": \"American Bar Association journal.\",\n \"parentInstances\": [],\n \"childInstances\": [],\n \"isBoundWith\": false,\n \"alternativeTitles\": [],\n \"editions\": [],\n \"series\": [],\n \"identifiers\": [],\n \"contributors\": [\n {\n \"authorityId\": null,\n \"contributorNameTypeId\": \"d376e36c-b759-4fed-8502-7130d1eeff39\",\n \"name\": \"American Bar Association\",\n \"contributorTypeId\": \"6e09d47d-95e2-4d8a-831b-f777b8ef6d81\",\n \"contributorTypeText\": \"\",\n \"primary\": null\n },\n {\n \"authorityId\": null,\n \"contributorNameTypeId\": \"d376e36c-b759-4fed-8502-7130d1eeff39\",\n \"name\": \"American Bar Association. Journal\",\n \"contributorTypeId\": \"06b2cbd8-66bf-4956-9d90-97c9776365a4\",\n \"contributorTypeText\": \"\",\n \"primary\": null\n }\n ],\n \"subjects\": [],\n \"classifications\": [],\n \"publication\": [],\n \"publicationFrequency\": [\n \"Monthly, 1921-83\",\n \"Quarterly, 1915-20\"\n ],\n \"publicationRange\": [\n \"Began with vol. 1, no. 1 (Jan. 1915); ceased with v. 69, [no.12] (Dec. 1983)\"\n ],\n \"electronicAccess\": [],\n \"instanceTypeId\": \"30fffe0e-e985-4144-b2e2-1e8179bdb41f\",\n \"instanceFormatIds\": [\n \"5cb91d15-96b1-4b8a-bf60-ec310538da66\"\n ],\n \"physicalDescriptions\": [\n \"69 v. : ill. ; 23-30 cm.\"\n ],\n \"languages\": [\n \"eng\"\n ],\n \"notes\": [],\n \"previouslyHeld\": false,\n \"discoverySuppress\": false,\n \"statisticalCodeIds\": [],\n \"metadata\": {\n \"createdDate\": \"2023-11-10T11:54:16.187+00:00\",\n \"createdByUserId\": \"cffb2565-07fc-470b-86c6-17d8ce14432e\",\n \"updatedDate\": \"2024-01-05T09:52:26.651+00:00\",\n \"updatedByUserId\": \"ca6022de-2644-46fe-b6f2-78df15483721\"\n },\n \"tags\": {\n \"tagList\": []\n },\n \"natureOfContentTermIds\": [\n\t\t\t\t\"921e6d93-bafb-4a02-b62f-dcd027c45406\"\n\t\t\t],\n \"precedingTitles\": [],\n \"succeedingTitles\": []\n }\n ],\n \"totalRecords\": 1\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ }
+ },
{
"request": {
"method": "GET",
diff --git a/src/test/resources/mappings/srs-record-invalid-content.json b/src/test/resources/mappings/srs-record-invalid-content.json
new file mode 100644
index 000000000..53d41aa15
--- /dev/null
+++ b/src/test/resources/mappings/srs-record-invalid-content.json
@@ -0,0 +1,17 @@
+{
+ "mappings": [
+ {
+ "request": {
+ "method": "GET",
+ "url": "/source-storage/source-records?instanceId=7772796a-b88b-4991-a9f7-2e368217c487&idType=INSTANCE"
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"sourceRecords\": [\n {\n \"recordId\": \"777fad9e-7f8e-4d8e-9a71-00d251817866\", \"parsedRecord\": {\n \"id\": \"8e5ea07d-0c06-4a3b-ab34-f4fc3f76bc09\",\n \"conten\": {\"ghf\": \"marc content\"} }}\n ],\n \"totalRecords\": 1\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ ]
+}
diff --git a/src/test/resources/mappings/srs-records.json b/src/test/resources/mappings/srs-records.json
index 1ec17317f..1c8c96768 100644
--- a/src/test/resources/mappings/srs-records.json
+++ b/src/test/resources/mappings/srs-records.json
@@ -7,7 +7,7 @@
},
"response": {
"status": 200,
- "body": "{\n \"sourceRecords\": [\n {\n \"recordId\": \"666fad9e-7f8e-4d8e-9a71-00d251817866\" }\n ],\n \"totalRecords\": 1\n}",
+ "body": "{\n \"sourceRecords\": [\n {\n \"recordId\": \"666fad9e-7f8e-4d8e-9a71-00d251817866\", \"parsedRecord\": {\n \"id\": \"8e5ea07d-0c06-4a3b-ab34-f4fc3f76bc09\",\n \"content\": {\"000\": \"marc content\"} }\n }\n ],\n \"totalRecords\": 1\n}",
"headers": {
"Content-Type": "application/json"
}
diff --git a/src/test/resources/upload/marc_instance_id_invalid_content.csv b/src/test/resources/upload/marc_instance_id_invalid_content.csv
new file mode 100644
index 000000000..e9dceccc2
--- /dev/null
+++ b/src/test/resources/upload/marc_instance_id_invalid_content.csv
@@ -0,0 +1 @@
+7772796a-b88b-4991-a9f7-2e368217c487