From 183e0bc9a6d353e349b8ef7e65a19158dd38746c Mon Sep 17 00:00:00 2001 From: Tsaghik Khachatryan Date: Tue, 26 Mar 2024 14:53:16 +0400 Subject: [PATCH 01/24] refactor(marc-bib-creation): keep the order during marc bib record creation Closes: MODINV-997 (cherry picked from commit 884093e28ac575113e6d83a50aab4b0c36d951b7) --- .../actions/CreateInstanceEventHandler.java | 11 +- .../dataimport/util/AdditionalFieldsUtil.java | 100 +++++++++++++++++- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java index 3456cb5da..f6b60979e 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java @@ -41,8 +41,7 @@ import static org.folio.ActionProfile.FolioRecord.MARC_BIBLIOGRAPHIC; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_CREATED; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_CREATED_READY_FOR_POST_PROCESSING; -import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.SUBFIELD_I; -import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_999; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.*; import static org.folio.inventory.dataimport.util.DataImportConstants.UNIQUE_ID_ERROR_MESSAGE; import static org.folio.inventory.dataimport.util.LoggerUtil.logParametersEventHandler; import static org.folio.inventory.domain.instances.Instance.HRID_KEY; @@ -93,6 +92,7 @@ public CompletableFuture handle(DataImportEventPayload d Context context = EventHandlingUtil.constructContext(dataImportEventPayload.getTenant(), dataImportEventPayload.getToken(), dataImportEventPayload.getOkapiUrl()); Record targetRecord = Json.decodeValue(payloadContext.get(EntityType.MARC_BIBLIOGRAPHIC.value()), Record.class); + var sourceContent = targetRecord.getParsedRecord().getContent().toString(); if (!Boolean.parseBoolean(payloadContext.get("acceptInstanceId")) && AdditionalFieldsUtil.getValue(targetRecord, TAG_999, SUBFIELD_I).isPresent()) { LOGGER.error(INSTANCE_CREATION_999_ERROR_MESSAGE); @@ -130,7 +130,12 @@ public CompletableFuture handle(DataImportEventPayload d return addInstance(mappedInstance, instanceCollection) .compose(createdInstance -> getPrecedingSucceedingTitlesHelper().createPrecedingSucceedingTitles(mappedInstance, context).map(createdInstance)) .compose(createdInstance -> executeFieldsManipulation(createdInstance, targetRecord)) - .compose(createdInstance -> saveRecordInSrsAndHandleResponse(dataImportEventPayload, targetRecord, createdInstance, instanceCollection, dataImportEventPayload.getTenant())); + .compose(createdInstance -> { + var targetContent = targetRecord.getParsedRecord().getContent().toString(); + var content = reorderMarcRecordFields(sourceContent, targetContent); + targetRecord.setParsedRecord(targetRecord.getParsedRecord().withContent(content)); + return saveRecordInSrsAndHandleResponse(dataImportEventPayload, targetRecord, createdInstance, instanceCollection, dataImportEventPayload.getTenant()); + }); }) .onSuccess(ar -> { dataImportEventPayload.getContext().put(INSTANCE.value(), Json.encode(ar)); diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java index 56b7468df..1079aa918 100644 --- a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -1,5 +1,9 @@ package org.folio.inventory.dataimport.util; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.tuple.Pair; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; @@ -37,8 +41,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ForkJoinPool; import static java.lang.String.format; @@ -66,6 +69,8 @@ public final class AdditionalFieldsUtil { private static final String HR_ID_FIELD = "hrid"; private static final CacheLoader parsedRecordContentCacheLoader; private static final LoadingCache parsedRecordContentCache; + private static final ObjectMapper objectMapper = new ObjectMapper(); + public static final String FIELDS = "fields"; static { // this function is executed when creating a new item to be saved in the cache. @@ -641,4 +646,95 @@ public static void fillHrIdFieldInMarcRecord(Pair recordInst } AdditionalFieldsUtil.removeField(recordInstancePair.getKey(), TAG_003); } + + /** + * Reorders MARC record fields + * + * @param sourceContent source parsed record + * @param targetContent target parsed record + * @return MARC txt + */ + public static String reorderMarcRecordFields(String sourceContent, String targetContent) { + try { + var parsedContent = objectMapper.readTree(targetContent); + var fieldsArrayNode = (ArrayNode) parsedContent.path(FIELDS); + + var jsonNodesByTag = groupNodesByTag(fieldsArrayNode); + var sourceFields = getSourceFields(sourceContent); + var rearrangedArray = objectMapper.createArrayNode(); + + var nodes001 = jsonNodesByTag.get(TAG_001); + if (nodes001 != null && !nodes001.isEmpty()) { + rearrangedArray.addAll(nodes001); + jsonNodesByTag.remove(TAG_001); + } + + var nodes005 = jsonNodesByTag.get(TAG_005); + if (nodes005 != null && !nodes005.isEmpty()) { + rearrangedArray.addAll(nodes005); + jsonNodesByTag.remove(TAG_005); + } + + for (String tag : sourceFields) { + Queue nodes = jsonNodesByTag.get(tag); + if (nodes != null && !nodes.isEmpty()) { + rearrangedArray.addAll(nodes); + jsonNodesByTag.remove(tag); + } + + } + + jsonNodesByTag.values().forEach(rearrangedArray::addAll); + + ((ObjectNode) parsedContent).set(FIELDS, rearrangedArray); + return parsedContent.toString(); + } catch (Exception e) { + LOGGER.error("An error occurred while reordering Marc record fields: {}", e.getMessage(), e); + return targetContent; + } + } + + private static Map> groupNodesByTag(ArrayNode fieldsArrayNode) { + var jsonNodesByTag = new LinkedHashMap>(); + for (JsonNode node : fieldsArrayNode) { + var tag = getTagFromNode(node); + jsonNodesByTag.putIfAbsent(tag, new LinkedList<>()); + jsonNodesByTag.get(tag).add(node); + } + return jsonNodesByTag; + } + + private static String getTagFromNode(JsonNode node) { + return node.fieldNames().next(); + } + + private static List getSourceFields(String source) { + var sourceFields = new ArrayList(); + var remainingFields = new ArrayList(); + var has001 = false; + try { + var sourceJson = objectMapper.readTree(source); + var fieldsNode = sourceJson.get(FIELDS); + + for (JsonNode fieldNode : fieldsNode) { + var tag = fieldNode.fieldNames().next(); + if (tag.equals(TAG_001)) { + sourceFields.add(0, tag); + has001 = true; + } else if (tag.equals(TAG_005)) { + if (!has001) { + sourceFields.add(0, tag); + } else { + sourceFields.add(1, tag); + } + } else { + remainingFields.add(tag); + } + } + sourceFields.addAll(remainingFields); + } catch (Exception e) { + LOGGER.error("An error occurred while parsing source JSON: {}", e.getMessage(), e); + } + return sourceFields; + } } From cc6eac9ed99cdf3906ecc4ab448c20c2875c6224 Mon Sep 17 00:00:00 2001 From: Tsaghik Khachatryan Date: Tue, 26 Mar 2024 15:30:46 +0400 Subject: [PATCH 02/24] refactor(marc-bib-creation): update news.MD file Closes: MODINV-997 (cherry picked from commit 282d71d2ddba7347ded366b1ca3ae5f75f3568f3) --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index e827e6755..dc3eaf48a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -37,6 +37,7 @@ * Add subscription on DI_SRS_MARC_BIB_RECORD_UPDATED event about marc-bib update [MODSOURMAN-1106](https://folio-org.atlassian.net/browse/MODSOURMAN-1106) * Consortial: Local HRID gets added as 035 when instance is shared [MODINV-918](https://folio-org.atlassian.net/browse/MODINV-918) * Consortial: non-MARC data not saved when local source = MARC instance is promoted [MODINV-960](https://folio-org.atlassian.net/browse/MODINV-960) +* Keep order of MARC fields while Creating/Deriving/Editing MARC records [MODSOURMAN-1137](https://folio-org.atlassian.net/browse/MODSOURMAN-1137) ## 20.1.0 2023-10-13 * Update status when user attempts to update shared auth record from member tenant ([MODDATAIMP-926](https://issues.folio.org/browse/MODDATAIMP-926)) From 5008f7ee1b5972b95ae095e681c29f5c5cd5a36a Mon Sep 17 00:00:00 2001 From: Tsaghik Khachatryan Date: Tue, 26 Mar 2024 22:24:41 +0400 Subject: [PATCH 03/24] refactor(marc-bib-creation): added unit test for reorderMarcRecordFields method Closes: MODINV-997 (cherry picked from commit bb4f46fd7a69a2b51973579139458a056cf98e61) --- .../util/AdditionalFieldsUtilTest.java | 26 ++ src/test/resources/marc/parsedRecord.json | 335 ++++++++++++++++++ .../resources/marc/reorderedParsedRecord.json | 335 ++++++++++++++++++ .../marc/reorderingResultRecord.json | 335 ++++++++++++++++++ 4 files changed, 1031 insertions(+) create mode 100644 src/test/resources/marc/parsedRecord.json create mode 100644 src/test/resources/marc/reorderedParsedRecord.json create mode 100644 src/test/resources/marc/reorderingResultRecord.json diff --git a/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java index dc90c7510..70aa24597 100644 --- a/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java +++ b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java @@ -3,6 +3,7 @@ import com.github.benmanes.caffeine.cache.stats.CacheStats; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.folio.inventory.TestUtil; @@ -17,7 +18,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; +import org.marc4j.MarcException; +import java.io.File; import java.io.IOException; import java.time.Instant; import java.time.ZoneId; @@ -35,6 +38,9 @@ public class AdditionalFieldsUtilTest { private static final String PARSED_MARC_RECORD_PATH = "src/test/resources/marc/parsedMarcRecord.json"; + private static final String PARSED_RECORD = "src/test/resources/marc/parsedRecord.json"; + private static final String REORDERED_PARSED_RECORD = "src/test/resources/marc/reorderedParsedRecord.json"; + private static final String REORDERING_RESULT_RECORD = "src/test/resources/marc/reorderingResultRecord.json"; @Test public void shouldAddInstanceIdSubfield() throws IOException { @@ -633,4 +639,24 @@ public void shouldRemove035() { // then Assert.assertEquals(expectedParsedContent, parsedRecord.getContent()); } + + @Test + public void shouldReorderMarcRecordFields() throws IOException, MarcException { + var reorderedRecordContent = readFileFromPath(PARSED_RECORD); + var sourceRecordContent = readFileFromPath(REORDERED_PARSED_RECORD); + var reorderingResultRecord = readFileFromPath(REORDERING_RESULT_RECORD); + + var resultContent = AdditionalFieldsUtil.reorderMarcRecordFields(sourceRecordContent, reorderedRecordContent); + + assertNotNull(resultContent); + assertEquals(formatContent(resultContent), formatContent(reorderingResultRecord)); + } + + private static String readFileFromPath(String path) throws IOException { + return new String(FileUtils.readFileToByteArray(new File(path))); + } + + private String formatContent(String content) { + return content.replaceAll("\\s", ""); + } } diff --git a/src/test/resources/marc/parsedRecord.json b/src/test/resources/marc/parsedRecord.json new file mode 100644 index 000000000..4c6d25e55 --- /dev/null +++ b/src/test/resources/marc/parsedRecord.json @@ -0,0 +1,335 @@ +{ + "leader":"01314nam 22003851a 4500", + "fields":[ + { + "001":"ybp7406411" + }, + { + "005":"20120404100627.6" + }, + { + "003":"NhCcYBP" + }, + { + "006":"m||||||||d|||||||" + }, + { + "007":"cr||n|||||||||" + }, + { + "008":"120329s2011 sz a ob 001 0 eng d" + }, + { + "020":{ + "subfields":[ + { + "a":"2940447241 (electronic bk.)" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "020":{ + "subfields":[ + { + "a":"9782940447244 (electronic bk.)" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "040":{ + "subfields":[ + { + "a":"NhCcYBP" + }, + { + "c":"NhCcYBP" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "050":{ + "subfields":[ + { + "a":"Z246" + }, + { + "b":".A43 2011" + } + ], + "ind1":" ", + "ind2":"4" + } + }, + { + "082":{ + "subfields":[ + { + "a":"686.22" + }, + { + "2":"22" + } + ], + "ind1":"0", + "ind2":"4" + } + }, + { + "100":{ + "subfields":[ + { + "a":"Ambrose, Gavin." + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "245":{ + "subfields":[ + { + "a":"The fundamentals of typography" + }, + { + "h":"[electronic resource] /" + }, + { + "c":"Gavin Ambrose, Paul Harris." + } + ], + "ind1":"1", + "ind2":"4" + } + }, + { + "250":{ + "subfields":[ + { + "a":"2nd ed." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "260":{ + "subfields":[ + { + "a":"Lausanne ;" + }, + { + "a":"Worthing :" + }, + { + "b":"AVA Academia," + }, + { + "c":"2011." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "300":{ + "subfields":[ + { + "a":"1 online resource." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "490":{ + "subfields":[ + { + "a":"AVA Academia series" + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "500":{ + "subfields":[ + { + "a":"Previous ed.: 2006." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "504":{ + "subfields":[ + { + "a":"Includes bibliographical references (p. [200]) and index." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "505":{ + "subfields":[ + { + "a":"Type and language -- A few basics -- Letterforms -- Words and paragraphs -- Using type." + } + ], + "ind1":"0", + "ind2":" " + } + }, + { + "650":{ + "subfields":[ + { + "a":"Graphic design (Typography)" + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "650":{ + "subfields":[ + { + "a":"Printing." + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "700":{ + "subfields":[ + { + "a":"Harris, Paul," + }, + { + "d":"1971-" + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "710":{ + "subfields":[ + { + "a":"EBSCOhost" + } + ], + "ind1":"2", + "ind2":" " + } + }, + { + "776":{ + "subfields":[ + { + "c":"Original" + }, + { + "z":"9782940411764" + }, + { + "z":"294041176X" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "830":{ + "subfields":[ + { + "a":"AVA academia." + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "856":{ + "subfields":[ + { + "u":"http://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=430135" + } + ], + "ind1":"4", + "ind2":"0" + } + }, + { + "935":{ + "subfields":[ + { + "a":".o13465259" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "980":{ + "subfields":[ + { + "a":"130307" + }, + { + "b":"7107" + }, + { + "e":"7107" + }, + { + "f":"243965" + }, + { + "g":"1" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "981":{ + "subfields":[ + { + "b":"OM" + }, + { + "c":"nlnet" + } + ], + "ind1":" ", + "ind2":" " + } + } + ] +} diff --git a/src/test/resources/marc/reorderedParsedRecord.json b/src/test/resources/marc/reorderedParsedRecord.json new file mode 100644 index 000000000..341d2aa11 --- /dev/null +++ b/src/test/resources/marc/reorderedParsedRecord.json @@ -0,0 +1,335 @@ +{ + "leader":"01314nam 22003851a 4500", + "fields":[ + { + "040":{ + "subfields":[ + { + "a":"NhCcYBP" + }, + { + "c":"NhCcYBP" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "001":"ybp7406411" + }, + { + "003":"NhCcYBP" + }, + { + "006":"m||||||||d|||||||" + }, + { + "007":"cr||n|||||||||" + }, + { + "008":"120329s2011 sz a ob 001 0 eng d" + }, + { + "020":{ + "subfields":[ + { + "a":"2940447241 (electronic bk.)" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "020":{ + "subfields":[ + { + "a":"9782940447244 (electronic bk.)" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "050":{ + "subfields":[ + { + "a":"Z246" + }, + { + "b":".A43 2011" + } + ], + "ind1":" ", + "ind2":"4" + } + }, + { + "082":{ + "subfields":[ + { + "a":"686.22" + }, + { + "2":"22" + } + ], + "ind1":"0", + "ind2":"4" + } + }, + { + "100":{ + "subfields":[ + { + "a":"Ambrose, Gavin." + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "245":{ + "subfields":[ + { + "a":"The fundamentals of typography" + }, + { + "h":"[electronic resource] /" + }, + { + "c":"Gavin Ambrose, Paul Harris." + } + ], + "ind1":"1", + "ind2":"4" + } + }, + { + "250":{ + "subfields":[ + { + "a":"2nd ed." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "260":{ + "subfields":[ + { + "a":"Lausanne ;" + }, + { + "a":"Worthing :" + }, + { + "b":"AVA Academia," + }, + { + "c":"2011." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "300":{ + "subfields":[ + { + "a":"1 online resource." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "490":{ + "subfields":[ + { + "a":"AVA Academia series" + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "500":{ + "subfields":[ + { + "a":"Previous ed.: 2006." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "504":{ + "subfields":[ + { + "a":"Includes bibliographical references (p. [200]) and index." + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "505":{ + "subfields":[ + { + "a":"Type and language -- A few basics -- Letterforms -- Words and paragraphs -- Using type." + } + ], + "ind1":"0", + "ind2":" " + } + }, + { + "650":{ + "subfields":[ + { + "a":"Graphic design (Typography)" + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "650":{ + "subfields":[ + { + "a":"Printing." + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "700":{ + "subfields":[ + { + "a":"Harris, Paul," + }, + { + "d":"1971-" + } + ], + "ind1":"1", + "ind2":" " + } + }, + { + "710":{ + "subfields":[ + { + "a":"EBSCOhost" + } + ], + "ind1":"2", + "ind2":" " + } + }, + { + "776":{ + "subfields":[ + { + "c":"Original" + }, + { + "z":"9782940411764" + }, + { + "z":"294041176X" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "830":{ + "subfields":[ + { + "a":"AVA academia." + } + ], + "ind1":" ", + "ind2":"0" + } + }, + { + "856":{ + "subfields":[ + { + "u":"http://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=430135" + } + ], + "ind1":"4", + "ind2":"0" + } + }, + { + "935":{ + "subfields":[ + { + "a":".o13465259" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "980":{ + "subfields":[ + { + "a":"130307" + }, + { + "b":"7107" + }, + { + "e":"7107" + }, + { + "f":"243965" + }, + { + "g":"1" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "981":{ + "subfields":[ + { + "b":"OM" + }, + { + "c":"nlnet" + } + ], + "ind1":" ", + "ind2":" " + } + }, + { + "005":"20120404100627.6" + } + ] +} diff --git a/src/test/resources/marc/reorderingResultRecord.json b/src/test/resources/marc/reorderingResultRecord.json new file mode 100644 index 000000000..07f27ce60 --- /dev/null +++ b/src/test/resources/marc/reorderingResultRecord.json @@ -0,0 +1,335 @@ +{ + "leader": "01314nam 22003851a 4500", + "fields": [ + { + "001": "ybp7406411" + }, + { + "005": "20120404100627.6" + }, + { + "040": { + "subfields": [ + { + "a": "NhCcYBP" + }, + { + "c": "NhCcYBP" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "003": "NhCcYBP" + }, + { + "006": "m||||||||d|||||||" + }, + { + "007": "cr||n|||||||||" + }, + { + "008": "120329s2011 sz a ob 001 0 eng d" + }, + { + "020": { + "subfields": [ + { + "a": "2940447241 (electronic bk.)" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "020": { + "subfields": [ + { + "a": "9782940447244 (electronic bk.)" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "050": { + "subfields": [ + { + "a": "Z246" + }, + { + "b": ".A43 2011" + } + ], + "ind1": " ", + "ind2": "4" + } + }, + { + "082": { + "subfields": [ + { + "a": "686.22" + }, + { + "2": "22" + } + ], + "ind1": "0", + "ind2": "4" + } + }, + { + "100": { + "subfields": [ + { + "a": "Ambrose, Gavin." + } + ], + "ind1": "1", + "ind2": " " + } + }, + { + "245": { + "subfields": [ + { + "a": "The fundamentals of typography" + }, + { + "h": "[electronic resource] /" + }, + { + "c": "Gavin Ambrose, Paul Harris." + } + ], + "ind1": "1", + "ind2": "4" + } + }, + { + "250": { + "subfields": [ + { + "a": "2nd ed." + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "260": { + "subfields": [ + { + "a": "Lausanne ;" + }, + { + "a": "Worthing :" + }, + { + "b": "AVA Academia," + }, + { + "c": "2011." + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "300": { + "subfields": [ + { + "a": "1 online resource." + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "490": { + "subfields": [ + { + "a": "AVA Academia series" + } + ], + "ind1": "1", + "ind2": " " + } + }, + { + "500": { + "subfields": [ + { + "a": "Previous ed.: 2006." + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "504": { + "subfields": [ + { + "a": "Includes bibliographical references (p. [200]) and index." + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "505": { + "subfields": [ + { + "a": "Type and language -- A few basics -- Letterforms -- Words and paragraphs -- Using type." + } + ], + "ind1": "0", + "ind2": " " + } + }, + { + "650": { + "subfields": [ + { + "a": "Graphic design (Typography)" + } + ], + "ind1": " ", + "ind2": "0" + } + }, + { + "650": { + "subfields": [ + { + "a": "Printing." + } + ], + "ind1": " ", + "ind2": "0" + } + }, + { + "700": { + "subfields": [ + { + "a": "Harris, Paul," + }, + { + "d": "1971-" + } + ], + "ind1": "1", + "ind2": " " + } + }, + { + "710": { + "subfields": [ + { + "a": "EBSCOhost" + } + ], + "ind1": "2", + "ind2": " " + } + }, + { + "776": { + "subfields": [ + { + "c": "Original" + }, + { + "z": "9782940411764" + }, + { + "z": "294041176X" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "830": { + "subfields": [ + { + "a": "AVA academia." + } + ], + "ind1": " ", + "ind2": "0" + } + }, + { + "856": { + "subfields": [ + { + "u": "http://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=430135" + } + ], + "ind1": "4", + "ind2": "0" + } + }, + { + "935": { + "subfields": [ + { + "a": ".o13465259" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "980": { + "subfields": [ + { + "a": "130307" + }, + { + "b": "7107" + }, + { + "e": "7107" + }, + { + "f": "243965" + }, + { + "g": "1" + } + ], + "ind1": " ", + "ind2": " " + } + }, + { + "981": { + "subfields": [ + { + "b": "OM" + }, + { + "c": "nlnet" + } + ], + "ind1": " ", + "ind2": " " + } + } + ] +} From 2c50e6276f4c3d0c9dc188e6ef84d29c68b6ceb1 Mon Sep 17 00:00:00 2001 From: Tsaghik Khachatryan Date: Wed, 27 Mar 2024 16:54:15 +0400 Subject: [PATCH 04/24] refactor(marc-bib-creation): added unit test for reorderMarcRecordFields method Closes: MODINV-997 (cherry picked from commit 52cfdf9eac375228121a62f35a4dffa936d2793f) --- .../util/AdditionalFieldsUtilTest.java | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java index 70aa24597..5f3786f51 100644 --- a/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java +++ b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java @@ -30,7 +30,17 @@ import java.util.UUID; import java.util.stream.IntStream; -import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.*; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_001; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_005; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_999; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.addControlledFieldToMarcRecord; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.addDataFieldToMarcRecord; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.addFieldToMarcRecord; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.dateTime005Formatter; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.getCacheStats; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.getValueFromControlledField; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.isFieldExist; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.removeField; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.Assert.*; @@ -53,8 +63,8 @@ public void shouldAddInstanceIdSubfield() throws IOException { String leader = new JsonObject(parsedRecordContent).getString("leader"); Record record = new Record().withId(recordId).withParsedRecord(parsedRecord); // when - boolean addedSourceRecordId = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 's', recordId); - boolean addedInstanceId = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean addedSourceRecordId = addFieldToMarcRecord(record, TAG_999, 's', recordId); + boolean addedInstanceId = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertTrue(addedSourceRecordId); Assert.assertTrue(addedInstanceId); @@ -66,8 +76,8 @@ public void shouldAddInstanceIdSubfield() throws IOException { int totalFieldsCount = 0; for (int i = fields.size(); i-- > 0; ) { JsonObject targetField = fields.getJsonObject(i); - if (targetField.containsKey(AdditionalFieldsUtil.TAG_999)) { - JsonArray subfields = targetField.getJsonObject(AdditionalFieldsUtil.TAG_999).getJsonArray("subfields"); + if (targetField.containsKey(TAG_999)) { + JsonArray subfields = targetField.getJsonObject(TAG_999).getJsonArray("subfields"); for (int j = subfields.size(); j-- > 0; ) { JsonObject targetSubfield = subfields.getJsonObject(j); if (targetSubfield.containsKey("i")) { @@ -91,7 +101,7 @@ public void shouldNotAddInstanceIdSubfieldIfNoParsedRecordContent() { Record record = new Record(); String instanceId = UUID.randomUUID().toString(); // when - boolean added = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean added = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertFalse(added); Assert.assertNull(record.getParsedRecord()); @@ -105,7 +115,7 @@ public void shouldNotAddInstanceIdSubfieldIfNoFieldsInParsedRecordContent() { record.setParsedRecord(new ParsedRecord().withContent(content)); String instanceId = UUID.randomUUID().toString(); // when - boolean added = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean added = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertFalse(added); Assert.assertNotNull(record.getParsedRecord()); @@ -121,7 +131,7 @@ public void shouldNotAddInstanceIdSubfieldIfCanNotConvertParsedContentToJsonObje record.setParsedRecord(new ParsedRecord().withContent(content)); String instanceId = UUID.randomUUID().toString(); // when - boolean added = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean added = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertFalse(added); Assert.assertNotNull(record.getParsedRecord()); @@ -137,7 +147,7 @@ public void shouldNotAddInstanceIdSubfieldIfContentHasNoFields() { record.setParsedRecord(new ParsedRecord().withContent(content)); String instanceId = UUID.randomUUID().toString(); // when - boolean added = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean added = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertFalse(added); Assert.assertNotNull(record.getParsedRecord()); @@ -151,7 +161,7 @@ public void shouldNotAddInstanceIdSubfieldIfContentIsNull() { record.setParsedRecord(new ParsedRecord().withContent(null)); String instanceId = UUID.randomUUID().toString(); // when - boolean added = AdditionalFieldsUtil.addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId); + boolean added = addFieldToMarcRecord(record, TAG_999, 'i', instanceId); // then Assert.assertFalse(added); Assert.assertNotNull(record.getParsedRecord()); @@ -166,7 +176,7 @@ public void shouldRemoveField() throws IOException { String leader = new JsonObject(parsedRecordContent).getString("leader"); parsedRecord.setContent(parsedRecordContent); Record record = new Record().withId(recordId).withParsedRecord(parsedRecord); - boolean deleted = AdditionalFieldsUtil.removeField(record, "001"); + boolean deleted = removeField(record, "001"); Assert.assertTrue(deleted); JsonObject content = new JsonObject(parsedRecord.getContent().toString()); JsonArray fields = content.getJsonArray("fields"); @@ -184,7 +194,7 @@ public void shouldNotAddControlledFieldToMarcRecord() throws IOException { String parsedRecordContent = TestUtil.readFileFromPath(PARSED_MARC_RECORD_PATH); ParsedRecord parsedRecord = new ParsedRecord().withContent(parsedRecordContent); Record record = new Record().withId(recordId).withParsedRecord(parsedRecord); - boolean added = AdditionalFieldsUtil.addControlledFieldToMarcRecord(record, "002", "", null); + boolean added = addControlledFieldToMarcRecord(record, "002", "", null); Assert.assertFalse(added); } @@ -195,7 +205,7 @@ public void shouldAddControlledFieldToMarcRecord() throws IOException { ParsedRecord parsedRecord = new ParsedRecord().withContent(parsedRecordContent); String leader = new JsonObject(parsedRecordContent).getString("leader"); Record record = new Record().withId(recordId).withParsedRecord(parsedRecord); - boolean added = AdditionalFieldsUtil.addControlledFieldToMarcRecord( + boolean added = addControlledFieldToMarcRecord( record, "002", "test", AdditionalFieldsUtil::addControlledFieldToMarcRecord); Assert.assertTrue(added); JsonObject content = new JsonObject(parsedRecord.getContent().toString()); @@ -215,7 +225,7 @@ public void shouldReplaceControlledFieldInMarcRecord() throws IOException { ParsedRecord parsedRecord = new ParsedRecord().withContent(parsedRecordContent); String leader = new JsonObject(parsedRecordContent).getString("leader"); Record record = new Record().withId(recordId).withParsedRecord(parsedRecord); - boolean added = AdditionalFieldsUtil.addControlledFieldToMarcRecord( + boolean added = addControlledFieldToMarcRecord( record, "003", "test", AdditionalFieldsUtil::replaceOrAddControlledFieldInMarcRecord); Assert.assertTrue(added); JsonObject content = new JsonObject(parsedRecord.getContent().toString()); @@ -254,7 +264,7 @@ public void shouldAddFieldToMarcRecordInNumericalOrder() throws IOException { String leader = new JsonObject(parsedRecordContent).getString("leader"); Record record = new Record().withId(UUID.randomUUID().toString()).withParsedRecord(parsedRecord); // when - boolean added = AdditionalFieldsUtil.addDataFieldToMarcRecord(record, "035", ' ', ' ', 'a', instanceHrId); + boolean added = addDataFieldToMarcRecord(record, "035", ' ', ' ', 'a', instanceHrId); // then Assert.assertTrue(added); JsonObject content = new JsonObject(parsedRecord.getContent().toString()); @@ -284,7 +294,7 @@ public void shouldNotSortExistingFieldsWhenAddFieldToToMarcRecord() { ParsedRecord parsedRecord = new ParsedRecord().withContent(parsedContent); Record record = new Record().withId(UUID.randomUUID().toString()).withParsedRecord(parsedRecord); // when - boolean added = AdditionalFieldsUtil.addDataFieldToMarcRecord(record, "999", 'f', 'f', 'i', instanceId); + boolean added = addDataFieldToMarcRecord(record, "999", 'f', 'f', 'i', instanceId); // then Assert.assertTrue(added); Assert.assertEquals(expectedParsedContent, parsedRecord.getContent()); @@ -393,7 +403,7 @@ public void shouldNotUpdate005Field() { AdditionalFieldsUtil.updateLatestTransactionDate(record, new MappingParameters().withMarcFieldProtectionSettings(List.of(new MarcFieldProtectionSetting().withField("*").withData("*")))); - String actualDate = AdditionalFieldsUtil.getValueFromControlledField(record, TAG_005); + String actualDate = getValueFromControlledField(record, TAG_005); assertNotNull(actualDate); assertEquals("20141107001016.0", actualDate); } @@ -411,7 +421,7 @@ public void shouldUpdate005Field() { AdditionalFieldsUtil.updateLatestTransactionDate(record, new MappingParameters()); - String actualDate = AdditionalFieldsUtil.getValueFromControlledField(record, TAG_005); + String actualDate = getValueFromControlledField(record, TAG_005); assertNotNull(actualDate); assertEquals(expectedDate.substring(0, 10), actualDate.substring(0, 10)); } @@ -545,7 +555,7 @@ public void caching() throws IOException { Assert.assertEquals(2, cacheStats.missCount()); Assert.assertEquals(2, cacheStats.loadCount()); // add field to marc record - Assert.assertTrue(addFieldToMarcRecord(record, AdditionalFieldsUtil.TAG_999, 'i', instanceId)); + Assert.assertTrue(addFieldToMarcRecord(record, TAG_999, 'i', instanceId)); cacheStats = getCacheStats().minus(initialCacheStats); Assert.assertEquals(9, cacheStats.requestCount()); Assert.assertEquals(7, cacheStats.hitCount()); From 19af5fee916b5082fc388482686eadbd964c9ee0 Mon Sep 17 00:00:00 2001 From: Tsaghik Khachatryan Date: Fri, 29 Mar 2024 11:24:23 +0400 Subject: [PATCH 05/24] refactor(marc-bib-creation): refactoring Closes: MODINV-997 (cherry picked from commit fa6a47d9c553b2cb6deed76b13dd64a8bdf53e8d) --- .../folio/inventory/dataimport/util/AdditionalFieldsUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java index 1079aa918..b2bc2a038 100644 --- a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -717,7 +717,7 @@ private static List getSourceFields(String source) { var fieldsNode = sourceJson.get(FIELDS); for (JsonNode fieldNode : fieldsNode) { - var tag = fieldNode.fieldNames().next(); + var tag = getTagFromNode(fieldNode); if (tag.equals(TAG_001)) { sourceFields.add(0, tag); has001 = true; From 999bd327d39591a7d9c2a41493c4bbbb56d14ce5 Mon Sep 17 00:00:00 2001 From: Maksat <144414992+Maksat-Galymzhan@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:04:02 +0500 Subject: [PATCH 06/24] MODSOURCE-608: Missing metadata in quick-marc editor (#707) * MODSOURCE-608: Added user meta data to post processing (cherry picked from commit 98d2f907e0abfb915c8466f527e18791f7c122d2) --- NEWS.md | 3 +++ .../dataimport/consumers/DataImportKafkaHandler.java | 3 +++ .../handlers/actions/ReplaceInstanceEventHandler.java | 4 ++++ .../dataimport/util/AdditionalFieldsUtil.java | 9 +++++++++ .../actions/ReplaceInstanceEventHandlerTest.java | 11 ++++++++--- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index dc3eaf48a..c6bfbe3f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,6 @@ +## 20.3.0-SNAPSHOT 2024-xx-xx +* "PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile [MODSOURCE-608](https://folio-org.atlassian.net/browse/MODSOURCE-608) + ## 20.2.0 2023-03-20 * Inventory cannot process Holdings with virtual fields ([MODINV-941](https://issues.folio.org/browse/MODINV-941)) * Set instance record as deleted ([MODINV-883](https://issues.folio.org/browse/MODINV-883)) diff --git a/src/main/java/org/folio/inventory/dataimport/consumers/DataImportKafkaHandler.java b/src/main/java/org/folio/inventory/dataimport/consumers/DataImportKafkaHandler.java index 3c4044abd..61df80316 100644 --- a/src/main/java/org/folio/inventory/dataimport/consumers/DataImportKafkaHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/consumers/DataImportKafkaHandler.java @@ -91,6 +91,7 @@ public class DataImportKafkaHandler implements AsyncRecordHandler handle(KafkaConsumerRecord record) { Map headersMap = KafkaHeaderUtils.kafkaHeadersToMap(record.headers()); String recordId = headersMap.get(RECORD_ID_HEADER); String chunkId = headersMap.get(CHUNK_ID_HEADER); + String userId = headersMap.get(USER_ID_HEADER); String jobExecutionId = eventPayload.getJobExecutionId(); LOGGER.info("Data import event payload has been received with event type: {}, recordId: {} by jobExecution: {} and chunkId: {}", eventPayload.getEventType(), recordId, jobExecutionId, chunkId); eventPayload.getContext().put(RECORD_ID_HEADER, recordId); eventPayload.getContext().put(CHUNK_ID_HEADER, chunkId); + eventPayload.getContext().put(USER_ID_HEADER, userId); Context context = EventHandlingUtil.constructContext(eventPayload.getTenant(), eventPayload.getToken(), eventPayload.getOkapiUrl()); String jobProfileSnapshotId = eventPayload.getContext().get(PROFILE_SNAPSHOT_ID_KEY); diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java index a60c40d22..5efc4b104 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java @@ -78,6 +78,7 @@ public class ReplaceInstanceEventHandler extends AbstractInstanceEventHandler { public static final String INSTANCE_ID_TYPE = "INSTANCE"; public static final String CENTRAL_TENANT_INSTANCE_UPDATED_FLAG = "CENTRAL_TENANT_INSTANCE_UPDATED"; public static final String CENTRAL_TENANT_ID = "CENTRAL_TENANT_ID"; + private static final String USER_ID = "userId"; private final ConsortiumService consortiumService; public static final String MARC_BIB_RECORD_CREATED = "MARC_BIB_RECORD_CREATED"; @@ -311,6 +312,7 @@ private Future prepareAndExecuteMapping(DataImportEventPayload dataImportE private Future prepareRecordForMapping(DataImportEventPayload dataImportEventPayload, List marcFieldProtectionSettings, Instance instance, MappingParameters mappingParameters, String tenantId) { + String userId = (String) dataImportEventPayload.getAdditionalProperties().get(USER_ID); if (MARC_INSTANCE_SOURCE.equals(instance.getSource()) || CONSORTIUM_MARC.getValue().equals(instance.getSource())) { return getRecordByInstanceId(dataImportEventPayload, instance.getId(), tenantId) .compose(existingRecord -> { @@ -327,6 +329,7 @@ private Future prepareRecordForMapping(DataImportEventPayload dataImportEv String updatedIncomingRecord = Json.encode(incomingRecord); org.folio.rest.jaxrs.model.Record targetRecord = Json.decodeValue(updatedIncomingRecord, org.folio.rest.jaxrs.model.Record.class); + AdditionalFieldsUtil.setUpdatedBy(targetRecord, userId); AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); dataImportEventPayload.getContext().put(MARC_BIBLIOGRAPHIC.value(), Json.encode(targetRecord)); } else { @@ -338,6 +341,7 @@ private Future prepareRecordForMapping(DataImportEventPayload dataImportEv String marcBibAsJson = dataImportEventPayload.getContext().get(EntityType.MARC_BIBLIOGRAPHIC.value()); org.folio.rest.jaxrs.model.Record targetRecord = Json.decodeValue(marcBibAsJson, org.folio.rest.jaxrs.model.Record.class); + AdditionalFieldsUtil.setUpdatedBy(targetRecord, userId); AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); AdditionalFieldsUtil.move001To035(targetRecord); dataImportEventPayload.getContext().put(MARC_BIBLIOGRAPHIC.value(), Json.encode(targetRecord)); diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java index b2bc2a038..8cb6bd2a6 100644 --- a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -21,6 +21,7 @@ import org.folio.processing.exceptions.EventProcessingException; import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; import org.folio.rest.jaxrs.model.MarcFieldProtectionSetting; +import org.folio.rest.jaxrs.model.Metadata; import org.folio.rest.jaxrs.model.ParsedRecord; import org.folio.rest.jaxrs.model.Record; import org.marc4j.MarcJsonReader; @@ -737,4 +738,12 @@ private static List getSourceFields(String source) { } return sourceFields; } + + public static void setUpdatedBy(org.folio.rest.jaxrs.model.Record changedRecord, String userId) { + if (changedRecord.getMetadata() != null) { + changedRecord.getMetadata().setUpdatedByUserId(userId); + } else { + changedRecord.withMetadata(new Metadata().withUpdatedByUserId(userId)); + } + } } diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java index a21d75899..d37df149b 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java @@ -133,6 +133,7 @@ public class ReplaceInstanceEventHandlerTest { private static final Integer INSTANCE_VERSION = 1; private static final String INSTANCE_VERSION_AS_STRING = "1"; private static final String MARC_INSTANCE_SOURCE = "MARC"; + private static final String USER_ID = "userId"; private final String localTenant = "tenant"; private final String consortiumTenant = "consortiumTenant"; private final UUID instanceId = UUID.randomUUID(); @@ -277,7 +278,7 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException Buffer buffer = BufferImpl.buffer("{\"parsedRecord\":{" + "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + - "}}"); + "},\"metadata\":{\"updatedByUserId\":\"26c1a77f-fd82-401f-973c-0f8d7406d966\"}}"); HttpResponse respForPass = buildHttpResponseWithBuffer(buffer, HttpStatus.SC_OK); when(sourceStorageClient.putSourceStorageRecordsGenerationById(any(), any())).thenReturn(Future.succeededFuture(respForPass)); @@ -288,7 +289,8 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException .withTenant(TENANT_ID) .withOkapiUrl(mockServer.baseUrl()) .withToken(TOKEN) - .withJobExecutionId(UUID.randomUUID().toString()); + .withJobExecutionId(UUID.randomUUID().toString()) + .withAdditionalProperty(USER_ID, UUID.randomUUID().toString()); CompletableFuture future = replaceInstanceEventHandler.handle(dataImportEventPayload); DataImportEventPayload actualDataImportEventPayload = future.get(20, TimeUnit.SECONDS); @@ -308,6 +310,9 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException assertThat(createdInstance.getJsonArray("notes").getJsonObject(0).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getJsonArray("notes").getJsonObject(1).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getString("_version"), is(INSTANCE_VERSION_AS_STRING)); + Record sourceRecord = Json.decodeValue(actualDataImportEventPayload.getContext().get(MARC_BIBLIOGRAPHIC.value()), Record.class); + assertNotNull(sourceRecord.getMetadata().getUpdatedByUserId()); + verify(mockedClient, times(2)).post(any(URL.class), any(JsonObject.class)); verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(), eq(INSTANCE.value())); verify(1, getRequestedFor(new UrlPathPattern(new RegexPattern(MAPPING_METADATA_URL + "/.*"), true))); @@ -461,7 +466,7 @@ public void shouldProcessEventIfConsortiumInstance() throws InterruptedException assertThat(createdInstance.getJsonArray("notes").getJsonObject(1).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getString("_version"), is(INSTANCE_VERSION_AS_STRING)); verify(mockedClient, times(2)).post(any(URL.class), any(JsonObject.class)); - verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(),eq(INSTANCE.value())); + verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(), eq(INSTANCE.value())); verify(replaceInstanceEventHandler).getSourceStorageSnapshotsClient(any(), argThat(tenantId -> tenantId.equals(consortiumTenant))); verify(sourceStorageSnapshotsClient).postSourceStorageSnapshots(argThat(snapshot -> snapshot.getJobExecutionId().equals(record.getSnapshotId()))); verify(replaceInstanceEventHandler).getSourceStorageRecordsClient(any(), argThat(tenantId -> tenantId.equals(consortiumTenant))); From 938e88997d7cce513edad99a4243cba5030c01d6 Mon Sep 17 00:00:00 2001 From: Volodymyr Rohach Date: Thu, 4 Apr 2024 20:38:54 +0300 Subject: [PATCH 07/24] MODINV-1003: The result table is not displayed in the file details log. (#709) * MODINV-1003: The result table is not displayed in the file details log. * MODINV-1003: Imports improved. * MODINV-1003: NEWS updated. (cherry picked from commit 5adf4c46ff72a3677970946cae2e9fcff94c2c90) --- NEWS.md | 1 + .../actions/ReplaceInstanceEventHandler.java | 4 ---- .../dataimport/util/AdditionalFieldsUtil.java | 17 +++++++---------- .../ReplaceInstanceEventHandlerTest.java | 11 +++-------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/NEWS.md b/NEWS.md index c6bfbe3f8..7021d17b8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ ## 20.3.0-SNAPSHOT 2024-xx-xx * "PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile [MODSOURCE-608](https://folio-org.atlassian.net/browse/MODSOURCE-608) +* The result table is not displayed in the file details log [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) ## 20.2.0 2023-03-20 * Inventory cannot process Holdings with virtual fields ([MODINV-941](https://issues.folio.org/browse/MODINV-941)) diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java index 5efc4b104..a60c40d22 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java @@ -78,7 +78,6 @@ public class ReplaceInstanceEventHandler extends AbstractInstanceEventHandler { public static final String INSTANCE_ID_TYPE = "INSTANCE"; public static final String CENTRAL_TENANT_INSTANCE_UPDATED_FLAG = "CENTRAL_TENANT_INSTANCE_UPDATED"; public static final String CENTRAL_TENANT_ID = "CENTRAL_TENANT_ID"; - private static final String USER_ID = "userId"; private final ConsortiumService consortiumService; public static final String MARC_BIB_RECORD_CREATED = "MARC_BIB_RECORD_CREATED"; @@ -312,7 +311,6 @@ private Future prepareAndExecuteMapping(DataImportEventPayload dataImportE private Future prepareRecordForMapping(DataImportEventPayload dataImportEventPayload, List marcFieldProtectionSettings, Instance instance, MappingParameters mappingParameters, String tenantId) { - String userId = (String) dataImportEventPayload.getAdditionalProperties().get(USER_ID); if (MARC_INSTANCE_SOURCE.equals(instance.getSource()) || CONSORTIUM_MARC.getValue().equals(instance.getSource())) { return getRecordByInstanceId(dataImportEventPayload, instance.getId(), tenantId) .compose(existingRecord -> { @@ -329,7 +327,6 @@ private Future prepareRecordForMapping(DataImportEventPayload dataImportEv String updatedIncomingRecord = Json.encode(incomingRecord); org.folio.rest.jaxrs.model.Record targetRecord = Json.decodeValue(updatedIncomingRecord, org.folio.rest.jaxrs.model.Record.class); - AdditionalFieldsUtil.setUpdatedBy(targetRecord, userId); AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); dataImportEventPayload.getContext().put(MARC_BIBLIOGRAPHIC.value(), Json.encode(targetRecord)); } else { @@ -341,7 +338,6 @@ private Future prepareRecordForMapping(DataImportEventPayload dataImportEv String marcBibAsJson = dataImportEventPayload.getContext().get(EntityType.MARC_BIBLIOGRAPHIC.value()); org.folio.rest.jaxrs.model.Record targetRecord = Json.decodeValue(marcBibAsJson, org.folio.rest.jaxrs.model.Record.class); - AdditionalFieldsUtil.setUpdatedBy(targetRecord, userId); AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); AdditionalFieldsUtil.move001To035(targetRecord); dataImportEventPayload.getContext().put(MARC_BIBLIOGRAPHIC.value(), Json.encode(targetRecord)); diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java index 8cb6bd2a6..690cd07d5 100644 --- a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -21,7 +21,6 @@ import org.folio.processing.exceptions.EventProcessingException; import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; import org.folio.rest.jaxrs.model.MarcFieldProtectionSetting; -import org.folio.rest.jaxrs.model.Metadata; import org.folio.rest.jaxrs.model.ParsedRecord; import org.folio.rest.jaxrs.model.Record; import org.marc4j.MarcJsonReader; @@ -42,7 +41,13 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.Map; +import java.util.ArrayList; import java.util.concurrent.ForkJoinPool; import static java.lang.String.format; @@ -738,12 +743,4 @@ private static List getSourceFields(String source) { } return sourceFields; } - - public static void setUpdatedBy(org.folio.rest.jaxrs.model.Record changedRecord, String userId) { - if (changedRecord.getMetadata() != null) { - changedRecord.getMetadata().setUpdatedByUserId(userId); - } else { - changedRecord.withMetadata(new Metadata().withUpdatedByUserId(userId)); - } - } } diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java index d37df149b..a21d75899 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java @@ -133,7 +133,6 @@ public class ReplaceInstanceEventHandlerTest { private static final Integer INSTANCE_VERSION = 1; private static final String INSTANCE_VERSION_AS_STRING = "1"; private static final String MARC_INSTANCE_SOURCE = "MARC"; - private static final String USER_ID = "userId"; private final String localTenant = "tenant"; private final String consortiumTenant = "consortiumTenant"; private final UUID instanceId = UUID.randomUUID(); @@ -278,7 +277,7 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException Buffer buffer = BufferImpl.buffer("{\"parsedRecord\":{" + "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + - "},\"metadata\":{\"updatedByUserId\":\"26c1a77f-fd82-401f-973c-0f8d7406d966\"}}"); + "}}"); HttpResponse respForPass = buildHttpResponseWithBuffer(buffer, HttpStatus.SC_OK); when(sourceStorageClient.putSourceStorageRecordsGenerationById(any(), any())).thenReturn(Future.succeededFuture(respForPass)); @@ -289,8 +288,7 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException .withTenant(TENANT_ID) .withOkapiUrl(mockServer.baseUrl()) .withToken(TOKEN) - .withJobExecutionId(UUID.randomUUID().toString()) - .withAdditionalProperty(USER_ID, UUID.randomUUID().toString()); + .withJobExecutionId(UUID.randomUUID().toString()); CompletableFuture future = replaceInstanceEventHandler.handle(dataImportEventPayload); DataImportEventPayload actualDataImportEventPayload = future.get(20, TimeUnit.SECONDS); @@ -310,9 +308,6 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException assertThat(createdInstance.getJsonArray("notes").getJsonObject(0).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getJsonArray("notes").getJsonObject(1).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getString("_version"), is(INSTANCE_VERSION_AS_STRING)); - Record sourceRecord = Json.decodeValue(actualDataImportEventPayload.getContext().get(MARC_BIBLIOGRAPHIC.value()), Record.class); - assertNotNull(sourceRecord.getMetadata().getUpdatedByUserId()); - verify(mockedClient, times(2)).post(any(URL.class), any(JsonObject.class)); verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(), eq(INSTANCE.value())); verify(1, getRequestedFor(new UrlPathPattern(new RegexPattern(MAPPING_METADATA_URL + "/.*"), true))); @@ -466,7 +461,7 @@ public void shouldProcessEventIfConsortiumInstance() throws InterruptedException assertThat(createdInstance.getJsonArray("notes").getJsonObject(1).getString("instanceNoteTypeId"), notNullValue()); assertThat(createdInstance.getString("_version"), is(INSTANCE_VERSION_AS_STRING)); verify(mockedClient, times(2)).post(any(URL.class), any(JsonObject.class)); - verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(), eq(INSTANCE.value())); + verify(sourceStorageClient).getSourceStorageRecordsFormattedById(anyString(),eq(INSTANCE.value())); verify(replaceInstanceEventHandler).getSourceStorageSnapshotsClient(any(), argThat(tenantId -> tenantId.equals(consortiumTenant))); verify(sourceStorageSnapshotsClient).postSourceStorageSnapshots(argThat(snapshot -> snapshot.getJobExecutionId().equals(record.getSnapshotId()))); verify(replaceInstanceEventHandler).getSourceStorageRecordsClient(any(), argThat(tenantId -> tenantId.equals(consortiumTenant))); From cbcf66e2008221a1ae7d3e8cccfaa145c93b7c35 Mon Sep 17 00:00:00 2001 From: Javokhir Abdullaev <101543142+JavokhirAbdullayev@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:03:12 +0500 Subject: [PATCH 08/24] MODINV-1001 The sorting for Items on Instance details page is not worked (#711) (cherry picked from commit 15f8cb5a339a98985c6d6f72473283a5619a6e18) --- NEWS.md | 1 + .../inventory/resources/ItemsByHoldingsRecordId.java | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 7021d17b8..3136a32a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,5 @@ ## 20.3.0-SNAPSHOT 2024-xx-xx +* The sorting for Items on Instance details page is not worked [MODINV-1001](https://folio-org.atlassian.net/browse/MODINV-1001) * "PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile [MODSOURCE-608](https://folio-org.atlassian.net/browse/MODSOURCE-608) * The result table is not displayed in the file details log [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) diff --git a/src/main/java/org/folio/inventory/resources/ItemsByHoldingsRecordId.java b/src/main/java/org/folio/inventory/resources/ItemsByHoldingsRecordId.java index 622868ef8..030eb9410 100644 --- a/src/main/java/org/folio/inventory/resources/ItemsByHoldingsRecordId.java +++ b/src/main/java/org/folio/inventory/resources/ItemsByHoldingsRecordId.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -95,7 +94,7 @@ private void getSingleTitleAndMultiTitleItems(RoutingContext routingContext) { webContext, routingContext)) .thenCompose(listOfItems -> maybeAppendSingleTitleItems(listOfItems, holdingsRecordId, webContext, routingContext)) - .thenAccept(combinedListOfItems -> sortAndRespond(combinedListOfItems, + .thenAccept(combinedListOfItems -> respondWithPagedManyItems(combinedListOfItems, webContext, routingContext)); } @@ -177,12 +176,9 @@ private CompletableFuture> maybeAppendSingleTitleItems( return futureListOfItems; } - private void sortAndRespond(List itemList, WebContext webContext, RoutingContext routingContext) { + private void respondWithPagedManyItems(List itemList, WebContext webContext, RoutingContext routingContext) { PagingParameters pagingParameters = getPagingParameters(webContext); - List sortedList = itemList.stream().sorted( - Comparator.comparing(i -> i.getBarcode() == null ? "~" : i.getBarcode())) - .toList(); - MultipleRecords items = new MultipleRecords<>(getPage(sortedList, + MultipleRecords items = new MultipleRecords<>(getPage(itemList, pagingParameters.offset, pagingParameters.limit), itemList.size()); respondWithManyItems(routingContext, webContext, items); } From 230bcae6c32f3edac71554f7ea6b7ace3529e0c3 Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Fri, 12 Apr 2024 14:15:51 +0300 Subject: [PATCH 09/24] update news --- NEWS.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3136a32a6..640214db0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,8 @@ -## 20.3.0-SNAPSHOT 2024-xx-xx -* The sorting for Items on Instance details page is not worked [MODINV-1001](https://folio-org.atlassian.net/browse/MODINV-1001) -* "PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile [MODSOURCE-608](https://folio-org.atlassian.net/browse/MODSOURCE-608) -* The result table is not displayed in the file details log [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) +## 20.2.1 2024-04-12 +* [MODINV-1001](https://folio-org.atlassian.net/browse/MODINV-1001) Fix sorting for Items on Instance details page +* [MODINV-999](https://folio-org.atlassian.net/browse/MODINV-999)"PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile +* [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) The result table is not displayed in the file details log +* [MODINV-997](https://folio-org.atlassian.net/browse/MODINV-997) Keep order of MARC fields while Creating/Deriving/Editing MARC records ## 20.2.0 2023-03-20 * Inventory cannot process Holdings with virtual fields ([MODINV-941](https://issues.folio.org/browse/MODINV-941)) @@ -42,7 +43,6 @@ * Add subscription on DI_SRS_MARC_BIB_RECORD_UPDATED event about marc-bib update [MODSOURMAN-1106](https://folio-org.atlassian.net/browse/MODSOURMAN-1106) * Consortial: Local HRID gets added as 035 when instance is shared [MODINV-918](https://folio-org.atlassian.net/browse/MODINV-918) * Consortial: non-MARC data not saved when local source = MARC instance is promoted [MODINV-960](https://folio-org.atlassian.net/browse/MODINV-960) -* Keep order of MARC fields while Creating/Deriving/Editing MARC records [MODSOURMAN-1137](https://folio-org.atlassian.net/browse/MODSOURMAN-1137) ## 20.1.0 2023-10-13 * Update status when user attempts to update shared auth record from member tenant ([MODDATAIMP-926](https://issues.folio.org/browse/MODDATAIMP-926)) From daeae001e71e0c1ba7ffc17d75a11719ecc5c422 Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Fri, 12 Apr 2024 14:16:31 +0300 Subject: [PATCH 10/24] [maven-release-plugin] prepare release v20.2.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b90dfce90..27303e052 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.1-SNAPSHOT + 20.2.1 Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.0 + v20.2.1 From 8fb299a10cc94c5ff95c16ebf9913946685d954f Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Fri, 12 Apr 2024 14:16:31 +0300 Subject: [PATCH 11/24] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 27303e052..e3f7b1abd 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.1 + 20.2.2-SNAPSHOT Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.1 + v20.2.0 From cdec7baca31a4b2dafaaf8bfcdbcaac51262980e Mon Sep 17 00:00:00 2001 From: Dmytro Krutii Date: Wed, 17 Apr 2024 17:02:10 +0300 Subject: [PATCH 12/24] MODINV-1009 Save lastCheckIn on mark item (cherry picked from commit 87796e9b486031570ca65752dc7f0c59f5ad1b40) --- src/main/java/org/folio/inventory/domain/items/Item.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/folio/inventory/domain/items/Item.java b/src/main/java/org/folio/inventory/domain/items/Item.java index af3a0cf92..fe2b65af3 100644 --- a/src/main/java/org/folio/inventory/domain/items/Item.java +++ b/src/main/java/org/folio/inventory/domain/items/Item.java @@ -599,7 +599,8 @@ public Item changeStatus(ItemStatusName newStatus) { .withPurchaseOrderLineIdentifier(purchaseOrderLineIdentifier) .withIsBoundWith(this.isBoundWith) .withTags(tags) - .withCirculationNotes(circulationNotes); + .withCirculationNotes(circulationNotes) + .withLastCheckIn(this.lastCheckIn); } @Override From a8d77b01fbad2faa697b3bad9cfbbdf83b6b62d0 Mon Sep 17 00:00:00 2001 From: Volodymyr Rohach Date: Fri, 19 Apr 2024 13:34:14 +0300 Subject: [PATCH 13/24] MODINV-1012: Implementation for Validation for NatureContentTerms added for Instance. Tests added. (#715) * MODINV-1012: Implementation for Validation for NatureContentTerms added for Instance. Tests added. * MODINV-1012: Documentation added. * MODINV-1012: Documentation added. * MODINV-1012: Tests added. (cherry picked from commit 2019a172a7288b63254d570f1ce8ffa9e13f14e9) --- .../actions/CreateInstanceEventHandler.java | 16 +++- .../actions/ReplaceInstanceEventHandler.java | 9 ++ .../dataimport/util/ValidationUtil.java | 48 ++++++++++ .../CreateInstanceEventHandlerTest.java | 88 ++++++++++++++++++ .../ReplaceInstanceEventHandlerTest.java | 89 +++++++++++++++++++ .../dataimport/util/ValidationUtilTest.java | 34 +++++++ 6 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/folio/inventory/dataimport/util/ValidationUtil.java create mode 100644 src/test/java/org/folio/inventory/dataimport/util/ValidationUtilTest.java diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java index f6b60979e..b76e331d0 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java @@ -15,6 +15,7 @@ import org.folio.inventory.dataimport.services.OrderHelperService; import org.folio.inventory.dataimport.util.AdditionalFieldsUtil; import org.folio.inventory.dataimport.util.ParsedRecordUtil; +import org.folio.inventory.dataimport.util.ValidationUtil; import org.folio.inventory.domain.instances.Instance; import org.folio.inventory.domain.instances.InstanceCollection; import org.folio.inventory.domain.relationship.RecordToEntity; @@ -118,15 +119,24 @@ public CompletableFuture handle(DataImportEventPayload d .compose(v -> { InstanceCollection instanceCollection = storage.getInstanceCollection(context); JsonObject instanceAsJson = prepareInstance(dataImportEventPayload, instanceId, jobExecutionId); - List errors = EventHandlingUtil.validateJsonByRequiredFields(instanceAsJson, requiredFields); - if (!errors.isEmpty()) { - String msg = format("Mapped Instance is invalid: %s, by jobExecutionId: '%s' and recordId: '%s' and chunkId: '%s' ", errors, + List requiredFieldsErrors = EventHandlingUtil.validateJsonByRequiredFields(instanceAsJson, requiredFields); + if (!requiredFieldsErrors.isEmpty()) { + String msg = format("Mapped Instance is invalid: %s, by jobExecutionId: '%s' and recordId: '%s' and chunkId: '%s' ", requiredFieldsErrors, jobExecutionId, recordId, chunkId); LOGGER.warn(msg); return Future.failedFuture(msg); } Instance mappedInstance = Instance.fromJson(instanceAsJson); + + List invalidUUIDsErrors = ValidationUtil.validateUUIDs(mappedInstance); + if (!invalidUUIDsErrors.isEmpty()) { + String msg = format("Mapped Instance is invalid: %s, by jobExecutionId: '%s' and recordId: '%s' and chunkId: '%s' ", invalidUUIDsErrors, + jobExecutionId, recordId, chunkId); + LOGGER.warn(msg); + return Future.failedFuture(msg); + } + return addInstance(mappedInstance, instanceCollection) .compose(createdInstance -> getPrecedingSucceedingTitlesHelper().createPrecedingSucceedingTitles(mappedInstance, context).map(createdInstance)) .compose(createdInstance -> executeFieldsManipulation(createdInstance, targetRecord)) diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java index a60c40d22..bfc53b753 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandler.java @@ -17,6 +17,7 @@ import org.folio.inventory.dataimport.cache.MappingMetadataCache; import org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil; import org.folio.inventory.dataimport.util.AdditionalFieldsUtil; +import org.folio.inventory.dataimport.util.ValidationUtil; import org.folio.inventory.domain.instances.Instance; import org.folio.inventory.domain.instances.InstanceCollection; import org.folio.inventory.exceptions.NotFoundException; @@ -194,6 +195,14 @@ private void processInstanceUpdate(DataImportEventPayload dataImportEventPayload org.folio.rest.jaxrs.model.Record targetRecord = Json.decodeValue(marcBibAsJson, org.folio.rest.jaxrs.model.Record.class); Instance mappedInstance = Instance.fromJson(instanceAsJson); + List invalidUUIDsErrors = ValidationUtil.validateUUIDs(mappedInstance); + if (!invalidUUIDsErrors.isEmpty()) { + String msg = format("Mapped Instance is invalid: %s, by jobExecutionId: '%s' and recordId: '%s' and chunkId: '%s' ", invalidUUIDsErrors, + jobExecutionId, recordId, chunkId); + LOGGER.warn(msg); + return Future.failedFuture(msg); + } + return updateInstanceAndRetryIfOlExists(mappedInstance, instanceCollection, dataImportEventPayload) .compose(updatedInstance -> getPrecedingSucceedingTitlesHelper().getExistingPrecedingSucceedingTitles(mappedInstance, context)) .map(precedingSucceedingTitles -> precedingSucceedingTitles.stream() diff --git a/src/main/java/org/folio/inventory/dataimport/util/ValidationUtil.java b/src/main/java/org/folio/inventory/dataimport/util/ValidationUtil.java new file mode 100644 index 000000000..0f694302c --- /dev/null +++ b/src/main/java/org/folio/inventory/dataimport/util/ValidationUtil.java @@ -0,0 +1,48 @@ +package org.folio.inventory.dataimport.util; + +import org.folio.inventory.domain.instances.Instance; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Util for detailed validation different entities. + */ +public class ValidationUtil { + + private ValidationUtil() { + } + + /** + * Validate fields inside the Instance entity. Validation based on checking if specific fields were mapped as UUIDs. + * If not - then the list with errors will be returned. + * Example: "Value 'invalid not UUID value' is not a UUID for someFieldName field" + * @param instance target Instance for validation + * @return ArrayList with errors when the needed fields are NOT as UUID. + */ + public static List validateUUIDs(Instance instance) { + ArrayList errorMessages = new ArrayList<>(); + + //TODO: This will be extended for different fields and entities.That's why there are so many methods just for 1 field. + // Branch for it extending validation: MODINV-1012-extended + validateField(errorMessages, instance.getNatureOfContentTermIds(), "natureOfContentTermIds"); + + return errorMessages; + } + + private static void validateField(List errorMessages, List values, String fieldName) { + values.stream() + .filter(value -> !isUUID(value)) + .forEach(value -> errorMessages.add(String.format("Value '%s' is not a UUID for %s field", value, fieldName))); + } + + private static boolean isUUID(String value) { + try { + UUID.fromString(value); + return true; + } catch (Exception ex) { + return false; + } + } +} diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java index a2bd71660..d5ed8bc7e 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java @@ -41,6 +41,7 @@ import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; import org.folio.processing.mapping.mapper.reader.Reader; import org.folio.processing.mapping.mapper.reader.record.marc.MarcBibReaderFactory; +import org.folio.processing.value.ListValue; import org.folio.processing.value.MissingValue; import org.folio.processing.value.StringValue; import org.folio.rest.client.SourceStorageRecordsClient; @@ -172,6 +173,44 @@ public class CreateInstanceEventHandlerTest { .withContentType(MAPPING_PROFILE) .withContent(JsonObject.mapFrom(mappingProfile).getMap()))))); + private JobProfile jobProfileWithNatureOfContentTerm = new JobProfile() + .withId(UUID.randomUUID().toString()) + .withName("Create MARC Bibs with NatureOfContentTerm") + .withDataType(JobProfile.DataType.MARC); + + private ActionProfile actionProfileWithNatureOfContentTerm = new ActionProfile() + .withId(UUID.randomUUID().toString()) + .withName("Create preliminary Item with NatureOfContentTerm") + .withAction(ActionProfile.Action.CREATE) + .withFolioRecord(INSTANCE); + + private MappingProfile mappingProfileWithNatureOfContentTerm = new MappingProfile() + .withId(UUID.randomUUID().toString()) + .withName("Prelim item from MARC with NatureOfContentTerm") + .withIncomingRecordType(EntityType.MARC_BIBLIOGRAPHIC) + .withExistingRecordType(EntityType.INSTANCE) + .withMappingDetails(new MappingDetail() + .withMappingFields(Lists.newArrayList( + new MappingRule().withPath("instance.instanceTypeId").withValue("\"instanceTypeIdExpression\"").withEnabled("true"), + new MappingRule().withPath("instance.title").withValue("\"titleExpression\"").withEnabled("true"), + new MappingRule().withPath("instance.natureOfContentTermIds[]").withValue("\"not uuid\"").withEnabled("true").withRepeatableFieldAction(MappingRule.RepeatableFieldAction.EXTEND_EXISTING)))); + + private ProfileSnapshotWrapper profileSnapshotWrapperWithNatureOfContentTerm = new ProfileSnapshotWrapper() + .withId(UUID.randomUUID().toString()) + .withProfileId(jobProfileWithNatureOfContentTerm.getId()) + .withContentType(JOB_PROFILE) + .withContent(jobProfileWithNatureOfContentTerm) + .withChildSnapshotWrappers(Collections.singletonList( + new ProfileSnapshotWrapper() + .withProfileId(actionProfileWithNatureOfContentTerm.getId()) + .withContentType(ACTION_PROFILE) + .withContent(actionProfileWithNatureOfContentTerm) + .withChildSnapshotWrappers(Collections.singletonList( + new ProfileSnapshotWrapper() + .withProfileId(mappingProfileWithNatureOfContentTerm.getId()) + .withContentType(MAPPING_PROFILE) + .withContent(JsonObject.mapFrom(mappingProfileWithNatureOfContentTerm).getMap()))))); + private CreateInstanceEventHandler createInstanceEventHandler; @Before @@ -586,6 +625,55 @@ public void shouldNotProcessEventIfRequiredFieldIsEmpty() throws InterruptedExce future.get(5, TimeUnit.MILLISECONDS); } + @Test(expected = Exception.class) + public void shouldNotProcessEventIfNatureContentFieldIsNotUUID() throws InterruptedException, ExecutionException, TimeoutException { + Reader fakeReader = Mockito.mock(Reader.class); + + String instanceTypeId = "fe19bae4-da28-472b-be90-d442e2428ead"; + String recordId = "567859ad-505a-400d-a699-0028a1fdbf84"; + String instanceId = "4d4545df-b5ba-4031-a031-70b1c1b2fc5d"; + String title = "titleValue"; + RecordToEntity recordToInstance = RecordToEntity.builder().recordId(recordId).entityId(instanceId) + .build(); + + when(fakeReader.read(any(MappingRule.class))).thenReturn(StringValue.of(instanceTypeId), StringValue.of(title), ListValue.of(Lists.newArrayList("not uuid"))); + + when(fakeReaderFactory.createReader()).thenReturn(fakeReader); + + when(storage.getInstanceCollection(any())).thenReturn(instanceRecordCollection); + + when(instanceIdStorageService.store(any(), any(), any())).thenReturn(Future.succeededFuture(recordToInstance)); + + MappingManager.registerReaderFactory(fakeReaderFactory); + MappingManager.registerWriterFactory(new InstanceWriterFactory()); + + HashMap context = new HashMap<>(); + Record record = new Record().withParsedRecord(new ParsedRecord().withContent(PARSED_CONTENT)); + record.setId(recordId); + + context.put(MARC_BIBLIOGRAPHIC.value(), Json.encode(record)); + + Buffer buffer = BufferImpl.buffer("{\"parsedRecord\":{" + + "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + + "}}"); + HttpResponse resp = buildHttpResponseWithBuffer(buffer); + when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); + + DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() + .withEventType(DI_INVENTORY_INSTANCE_CREATED.value()) + .withContext(context) + .withCurrentNode(profileSnapshotWrapperWithNatureOfContentTerm.getChildSnapshotWrappers().get(0)) + .withTenant(TENANT_ID) + .withOkapiUrl(mockServer.baseUrl()) + .withToken(TOKEN) + .withJobExecutionId(UUID.randomUUID().toString()) + .withOkapiUrl(mockServer.baseUrl()); + + CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); + future.get(10, TimeUnit.SECONDS); + } + @Test(expected = Exception.class) public void shouldNotProcessEventIfRecordContains999field() throws InterruptedException, ExecutionException, TimeoutException { var recordId = UUID.randomUUID().toString(); diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java index a21d75899..d36460f71 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java @@ -41,6 +41,7 @@ import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; import org.folio.processing.mapping.mapper.reader.Reader; import org.folio.processing.mapping.mapper.reader.record.marc.MarcBibReaderFactory; +import org.folio.processing.value.ListValue; import org.folio.processing.value.MissingValue; import org.folio.processing.value.StringValue; import org.folio.rest.client.SourceStorageRecordsClient; @@ -198,6 +199,44 @@ public class ReplaceInstanceEventHandlerTest { .withContentType(MAPPING_PROFILE) .withContent(JsonObject.mapFrom(mappingProfile).getMap()))))); + private JobProfile jobProfileWithNatureOfContentTerm = new JobProfile() + .withId(UUID.randomUUID().toString()) + .withName("Create MARC Bibs with NatureOfContentTerm") + .withDataType(JobProfile.DataType.MARC); + + private ActionProfile actionProfileWithNatureOfContentTerm = new ActionProfile() + .withId(UUID.randomUUID().toString()) + .withName("Replace preliminary Item with NatureOfContentTerm") + .withAction(ActionProfile.Action.UPDATE) + .withFolioRecord(INSTANCE); + + private MappingProfile mappingProfileWithNatureOfContentTerm = new MappingProfile() + .withId(UUID.randomUUID().toString()) + .withName("Prelim item from MARC with NatureOfContentTerm") + .withIncomingRecordType(EntityType.MARC_BIBLIOGRAPHIC) + .withExistingRecordType(EntityType.INSTANCE) + .withMappingDetails(new MappingDetail() + .withMappingFields(Lists.newArrayList( + new MappingRule().withPath("instance.instanceTypeId").withValue("\"instanceTypeIdExpression\"").withEnabled("true"), + new MappingRule().withPath("instance.title").withValue("\"titleExpression\"").withEnabled("true"), + new MappingRule().withPath("instance.natureOfContentTermIds[]").withValue("\"not uuid\"").withEnabled("true").withRepeatableFieldAction(MappingRule.RepeatableFieldAction.EXTEND_EXISTING)))); + + private ProfileSnapshotWrapper profileSnapshotWrapperWithNatureOfContentTerm = new ProfileSnapshotWrapper() + .withId(UUID.randomUUID().toString()) + .withProfileId(jobProfileWithNatureOfContentTerm.getId()) + .withContentType(JOB_PROFILE) + .withContent(jobProfileWithNatureOfContentTerm) + .withChildSnapshotWrappers(Collections.singletonList( + new ProfileSnapshotWrapper() + .withProfileId(actionProfileWithNatureOfContentTerm.getId()) + .withContentType(ACTION_PROFILE) + .withContent(actionProfileWithNatureOfContentTerm) + .withChildSnapshotWrappers(Collections.singletonList( + new ProfileSnapshotWrapper() + .withProfileId(mappingProfileWithNatureOfContentTerm.getId()) + .withContentType(MAPPING_PROFILE) + .withContent(JsonObject.mapFrom(mappingProfileWithNatureOfContentTerm).getMap()))))); + private ReplaceInstanceEventHandler replaceInstanceEventHandler; private PrecedingSucceedingTitlesHelper precedingSucceedingTitlesHelper; @@ -824,6 +863,56 @@ public void shouldNotProcessEventIfRequiredFieldIsEmpty() throws InterruptedExce future.get(5, TimeUnit.MILLISECONDS); } + @Test(expected = ExecutionException.class) + public void shouldNotProcessEventIfNatureContentFieldIsNotUUID() throws InterruptedException, ExecutionException, TimeoutException { + Reader fakeReader = Mockito.mock(Reader.class); + + String instanceTypeId = UUID.randomUUID().toString(); + String title = "titleValue"; + + when(fakeReader.read(any(MappingRule.class))).thenReturn(StringValue.of(instanceTypeId), StringValue.of(title), ListValue.of(Lists.newArrayList("not uuid"))); + + when(fakeReaderFactory.createReader()).thenReturn(fakeReader); + + when(storage.getInstanceCollection(any())).thenReturn(instanceRecordCollection); + + MappingManager.registerReaderFactory(fakeReaderFactory); + MappingManager.registerWriterFactory(new InstanceWriterFactory()); + + HashMap context = new HashMap<>(); + Record record = new Record().withParsedRecord(new ParsedRecord().withContent(PARSED_CONTENT)); + context.put(MARC_BIBLIOGRAPHIC.value(), Json.encode(record)); + context.put(INSTANCE.value(), new JsonObject() + .put("id", instanceId) + .put("hrid", UUID.randomUUID().toString()) + .put("source", MARC_INSTANCE_SOURCE) + .put("_version", INSTANCE_VERSION) + .put("discoverySuppress", false) + .encode()); + + mockInstance(MARC_INSTANCE_SOURCE); + + Buffer buffer = BufferImpl.buffer("{\"parsedRecord\":{" + + "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + + "}}"); + HttpResponse respForPass = buildHttpResponseWithBuffer(buffer, HttpStatus.SC_OK); + when(sourceStorageClient.putSourceStorageRecordsGenerationById(any(), any())).thenReturn(Future.succeededFuture(respForPass)); + + DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() + .withEventType(DI_INVENTORY_INSTANCE_CREATED.value()) + .withContext(context) + .withCurrentNode(profileSnapshotWrapperWithNatureOfContentTerm.getChildSnapshotWrappers().get(0)) + .withTenant(TENANT_ID) + .withOkapiUrl(mockServer.baseUrl()) + .withToken(TOKEN) + .withJobExecutionId(UUID.randomUUID().toString()); + + CompletableFuture future = replaceInstanceEventHandler.handle(dataImportEventPayload); + future.get(10, TimeUnit.SECONDS); + } + + @Test public void shouldReturnFailedFutureIfCurrentActionProfileHasNoMappingProfile() { HashMap context = new HashMap<>(); diff --git a/src/test/java/org/folio/inventory/dataimport/util/ValidationUtilTest.java b/src/test/java/org/folio/inventory/dataimport/util/ValidationUtilTest.java new file mode 100644 index 000000000..69eba9a19 --- /dev/null +++ b/src/test/java/org/folio/inventory/dataimport/util/ValidationUtilTest.java @@ -0,0 +1,34 @@ +package org.folio.inventory.dataimport.util; + +import org.folio.inventory.domain.instances.Instance; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@RunWith(BlockJUnit4ClassRunner.class) +public class ValidationUtilTest { + + @Test + public void shouldHaveNoErrorIfNatureAreAsUUID() { + Instance instance = new Instance(UUID.randomUUID().toString(), "1", "001", "MARC", "Title", UUID.randomUUID().toString()); + instance.setNatureOfContentTermIds(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + List errors = ValidationUtil.validateUUIDs(instance); + Assert.assertEquals(0, errors.size()); + } + + @Test + public void shouldHaveSeveralErrorsIfSomeNatureAreNotAsUUID() { + Instance instance = new Instance(UUID.randomUUID().toString(), "1", "001", "MARC", "Title", UUID.randomUUID().toString()); + instance.setNatureOfContentTermIds(Arrays.asList(UUID.randomUUID().toString(), "not uuid value", UUID.randomUUID().toString(), UUID.randomUUID().toString(), "second not UUID value")); + List errors = ValidationUtil.validateUUIDs(instance); + Assert.assertEquals(2, errors.size()); + Assert.assertEquals("Value 'not uuid value' is not a UUID for natureOfContentTermIds field", errors.get(0)); + Assert.assertEquals("Value 'second not UUID value' is not a UUID for natureOfContentTermIds field", errors.get(1)); + + } +} From 1a515ae7c13ad5ce5ec2dfa018eed98fa9ae5e2e Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Mon, 22 Apr 2024 11:27:49 +0300 Subject: [PATCH 14/24] update news --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 640214db0..ed2b3e670 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +## 20.2.2 2024-04-22 +* [MODINV-1009](https://folio-org.atlassian.net/browse/MODINV-1009) Circulation History in Item record removed when Item is marked Missing/Long missing/ Unavailable +* [MODINV-1012](https://folio-org.atlassian.net/browse/MODINV-1012) Invalid values (as it is) created in JSON when value is not matching accepted options provided in Instance field mapping for Nature of Content term + ## 20.2.1 2024-04-12 * [MODINV-1001](https://folio-org.atlassian.net/browse/MODINV-1001) Fix sorting for Items on Instance details page * [MODINV-999](https://folio-org.atlassian.net/browse/MODINV-999)"PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile From 6f25dfad8b40fde378275eb74c0554dc468ab4aa Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Mon, 22 Apr 2024 11:28:48 +0300 Subject: [PATCH 15/24] [maven-release-plugin] prepare release v20.2.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e3f7b1abd..031bb6591 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.2-SNAPSHOT + 20.2.2 Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.0 + v20.2.2 From e74ce40ec58c9d1a507ebd5355d1d9e7f3a1d5b4 Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Mon, 22 Apr 2024 11:28:48 +0300 Subject: [PATCH 16/24] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 031bb6591..632ad331b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.2 + 20.2.3-SNAPSHOT Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.2 + v20.2.0 From 9dde9247f0d88a74875337bb9619018885299cfd Mon Sep 17 00:00:00 2001 From: Dmytro Krutii Date: Tue, 23 Apr 2024 16:31:45 +0300 Subject: [PATCH 17/24] MODINV-1006 Avoid null values in the ElectronicAccess object (cherry picked from commit 4a0516f778d459ba27746a55543e66bcf77a9332) --- NEWS.md | 10 ++- .../resources/ItemRepresentation.java | 7 ++- .../folio/inventory/support/JsonHelper.java | 62 ++++++++++++++++++- .../resources/ItemRepresentationTest.java | 41 +++++++++--- .../inventory/support/JsonHelperTest.java | 43 +++++++++++-- 5 files changed, 142 insertions(+), 21 deletions(-) diff --git a/NEWS.md b/NEWS.md index ed2b3e670..c9246bb21 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,15 @@ +## 20.2.3 2024-04-25 +* [MODINV-1022](https://folio-org.atlassian.net/browse/MODINV-1022) Circulation History in Item record removed when Item is marked Missing/Long missing/ Unavailable + ## 20.2.2 2024-04-22 * [MODINV-1009](https://folio-org.atlassian.net/browse/MODINV-1009) Circulation History in Item record removed when Item is marked Missing/Long missing/ Unavailable * [MODINV-1012](https://folio-org.atlassian.net/browse/MODINV-1012) Invalid values (as it is) created in JSON when value is not matching accepted options provided in Instance field mapping for Nature of Content term ## 20.2.1 2024-04-12 * [MODINV-1001](https://folio-org.atlassian.net/browse/MODINV-1001) Fix sorting for Items on Instance details page -* [MODINV-999](https://folio-org.atlassian.net/browse/MODINV-999)"PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile -* [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) The result table is not displayed in the file details log -* [MODINV-997](https://folio-org.atlassian.net/browse/MODINV-997) Keep order of MARC fields while Creating/Deriving/Editing MARC records +* [MODINV-999](https://folio-org.atlassian.net/browse/MODINV-999)"PMSystem" displayed as source in "quickmarc" view when record was created by "Non-matches" action of job profile +* [MODINV-1003](https://folio-org.atlassian.net/browse/MODINV-1003) The result table is not displayed in the file details log +* [MODINV-997](https://folio-org.atlassian.net/browse/MODINV-997) Keep order of MARC fields while Creating/Deriving/Editing MARC records ## 20.2.0 2023-03-20 * Inventory cannot process Holdings with virtual fields ([MODINV-941](https://issues.folio.org/browse/MODINV-941)) @@ -47,6 +50,7 @@ * Add subscription on DI_SRS_MARC_BIB_RECORD_UPDATED event about marc-bib update [MODSOURMAN-1106](https://folio-org.atlassian.net/browse/MODSOURMAN-1106) * Consortial: Local HRID gets added as 035 when instance is shared [MODINV-918](https://folio-org.atlassian.net/browse/MODINV-918) * Consortial: non-MARC data not saved when local source = MARC instance is promoted [MODINV-960](https://folio-org.atlassian.net/browse/MODINV-960) +* Keep order of MARC fields while Creating/Deriving/Editing MARC records [MODSOURMAN-1137](https://folio-org.atlassian.net/browse/MODSOURMAN-1137) ## 20.1.0 2023-10-13 * Update status when user attempts to update shared auth record from member tenant ([MODDATAIMP-926](https://issues.folio.org/browse/MODDATAIMP-926)) diff --git a/src/main/java/org/folio/inventory/resources/ItemRepresentation.java b/src/main/java/org/folio/inventory/resources/ItemRepresentation.java index fce84872a..2e37cd635 100644 --- a/src/main/java/org/folio/inventory/resources/ItemRepresentation.java +++ b/src/main/java/org/folio/inventory/resources/ItemRepresentation.java @@ -16,6 +16,7 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; +import org.folio.inventory.support.JsonHelper; class ItemRepresentation { JsonObject toJson(MultipleRecords wrappedItems) { @@ -130,7 +131,7 @@ private JsonObject toJson( includeIfPresent(representation, Item.ITEM_IDENTIFIER_KEY, item.getItemIdentifier()); includeIfPresent(representation,Item.TAGS_KEY, new JsonObject().put(Item.TAG_LIST_KEY, new JsonArray(item.getTags()))); representation.put(Item.YEAR_CAPTION_KEY, item.getYearCaption()); - representation.put(Item.ELECTRONIC_ACCESS_KEY, item.getElectronicAccess()); + JsonHelper.putNotNullValues(representation, Item.ELECTRONIC_ACCESS_KEY, item.getElectronicAccess()); representation.put(Item.STATISTICAL_CODE_IDS_KEY, item.getStatisticalCodeIds()); representation.put(Item.PURCHASE_ORDER_LINE_IDENTIFIER, item.getPurchaseOrderLineIdentifier()); includeReferenceIfPresent(representation, "materialType", @@ -244,7 +245,7 @@ private void includeIfPresent( String propertyName, JsonArray propertyValue) { if (propertyValue != null) { - representation.put( propertyName, propertyValue ); + representation.put(propertyName, propertyValue); } } @@ -257,7 +258,7 @@ private void includeIfPresent( if (from != null) { String value = propertyValue.apply(from); - if(value != null) { + if (value != null) { representation.put(propertyName, value); } } diff --git a/src/main/java/org/folio/inventory/support/JsonHelper.java b/src/main/java/org/folio/inventory/support/JsonHelper.java index efe0a11f6..0ec9e8635 100644 --- a/src/main/java/org/folio/inventory/support/JsonHelper.java +++ b/src/main/java/org/folio/inventory/support/JsonHelper.java @@ -1,13 +1,19 @@ package org.folio.inventory.support; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import liquibase.util.StringUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; -import io.vertx.core.json.JsonObject; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class JsonHelper { public static String getNestedProperty( @@ -41,6 +47,25 @@ public JsonObject getJsonFileAsJsonObject(String filePath) throws IOException { return new JsonObject(readFile(is)); } + /** + * Update JsonObject representation with the given property name and only not null values. + * + * @param representation The JsonObject representation to update. + * @param propertyName The name of the property to set. + * @param obj The value to set the property with. + */ + public static void putNotNullValues(JsonObject representation, String propertyName, Object obj) { + if (obj != null && isNotBlank(propertyName)) { + if (obj instanceof Collection) { + representation.put(propertyName, new JsonArray(toNotNullList((Collection) obj))); + } else { + JsonObject json = JsonObject.mapFrom(obj); + handleNullNestedFields(json); + representation.put(propertyName, json); + } + } + } + private String readFile(InputStream is) throws IOException { try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { StringBuilder sb = new StringBuilder(); @@ -53,4 +78,37 @@ private String readFile(InputStream is) throws IOException { return sb.toString(); } } + + private static JsonObject handleNullNestedFields(JsonObject itemObject) { + var keysToRemove = new ArrayList(); + for (var key : itemObject.fieldNames()) { + var value = itemObject.getValue(key); + if (value == null) { + keysToRemove.add(key); + } else if (value instanceof String && StringUtil.isEmpty((String) value)) { + keysToRemove.add(key); + } else if (value instanceof JsonObject) { + handleNullNestedFields((JsonObject) value); + } + } + + keysToRemove.forEach(itemObject::remove); + return itemObject; + } + + private static List> toNotNullList(Collection collection) { + return collection.stream() + .filter(Objects::nonNull) + .map(item -> { + if (item instanceof Collection) { + return toNotNullList((Collection) item); + } else { + var jsonItem = JsonObject.mapFrom(item); + handleNullNestedFields(jsonItem); + return jsonItem; + } + }) + .toList(); + } + } diff --git a/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java b/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java index 36414e0cf..380cc7d63 100644 --- a/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java +++ b/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java @@ -1,17 +1,19 @@ package org.folio.inventory.resources; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import java.util.UUID; - +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import org.folio.inventory.domain.items.Item; import org.folio.inventory.domain.items.ItemStatusName; import org.folio.inventory.domain.items.Status; +import org.folio.inventory.domain.sharedproperties.ElectronicAccess; import org.junit.Test; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; public class ItemRepresentationTest { @Test @@ -23,4 +25,29 @@ public void jsonContainsVersion() { .toJson(item, null, instance, null, null, null, null, null, null); assertThat(json.getString("_version"), is("123")); } + + @Test + public void jsonWithoutNullOrEmptyValues() { + var testValue = "https://test.com"; + var electronicAccessKey = "electronicAccess"; + var item = new Item(UUID.randomUUID().toString(), null, null, + new Status(ItemStatusName.AVAILABLE), null, null, null); + var electronicAccess = new ElectronicAccess(testValue, "", null, null, null); + item.withElectronicAccess(List.of(electronicAccess)); + var json = new ItemRepresentation() + .toJson(item, null, new JsonObject().put("contributors", new JsonArray()), null, null, null, null, null, null); + var electronicAccessObject = json.getJsonArray(electronicAccessKey); + + var nullableList = new ArrayList(); + nullableList.add(null); + item.withElectronicAccess(nullableList); + var nullElectronicAccessJson = new ItemRepresentation() + .toJson(item, null, new JsonObject().put("contributors", new JsonArray()), null, null, null, null, null, null); + var emptyElectronicAccessObject = nullElectronicAccessJson.getJsonArray(electronicAccessKey); + + assertThat(electronicAccessObject.size(), is(1)); + assertThat(electronicAccessObject.getJsonObject(0).fieldNames().size(), is(1)); + assertThat(electronicAccessObject.getJsonObject(0).getValue("uri"), is(testValue)); + assertThat(emptyElectronicAccessObject, is(new JsonArray())); + } } diff --git a/src/test/java/org/folio/inventory/support/JsonHelperTest.java b/src/test/java/org/folio/inventory/support/JsonHelperTest.java index 690a2e340..d5b442b84 100644 --- a/src/test/java/org/folio/inventory/support/JsonHelperTest.java +++ b/src/test/java/org/folio/inventory/support/JsonHelperTest.java @@ -1,17 +1,22 @@ package org.folio.inventory.support; -import static org.folio.inventory.support.JsonHelper.includeIfPresent; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - import io.vertx.core.json.JsonObject; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; + +import static org.folio.inventory.support.JsonHelper.includeIfPresent; +import static org.folio.inventory.support.JsonHelper.putNotNullValues; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + @RunWith(MockitoJUnitRunner.class) public class JsonHelperTest { @Spy @@ -43,4 +48,30 @@ public void shouldNotIncludeIfValueIsNull() { includeIfPresent(representation, "key", null); verifyZeroInteractions(representation); } + + @Test + public void shouldNotIncludeNullOeEmptyValues() { + var nutNullString = "notNull"; + var key = "key"; + var rootKey = "root"; + var arrayKey = "array"; + var value = new JsonObject(); + var nestedValue = new JsonObject(); + var list = new ArrayList(); + + nestedValue.put(nutNullString, nutNullString); + nestedValue.put("null", null); + nestedValue.put("empty", ""); + value.put(key, nestedValue); + list.add(nestedValue); + list.add(null); + putNotNullValues(representation, rootKey, value); + putNotNullValues(representation, arrayKey, list); + + var objResult = representation.getJsonObject(rootKey).getJsonObject(key); + var listResult = representation.getJsonArray(arrayKey); + assertThat(objResult.size(), is(1)); + assertThat(objResult.getValue(nutNullString), is(nutNullString)); + assertThat(listResult.size(), is(1)); + } } From 4ff637b76c02c9ea5563208ca5dd02fc4be68ce4 Mon Sep 17 00:00:00 2001 From: Dmytro Krutii Date: Wed, 24 Apr 2024 20:14:19 +0300 Subject: [PATCH 18/24] MODINV-1006 Avoid null values in the ElectronicAccess object for instance (cherry picked from commit 53abb78ebf6f54e8fbba85cdff7f6c18e54e3ea7) --- .../inventory/domain/instances/Instance.java | 3 ++- .../folio/inventory/support/JsonHelper.java | 19 +++++++++++-------- .../inventory/support/JsonHelperTest.java | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/folio/inventory/domain/instances/Instance.java b/src/main/java/org/folio/inventory/domain/instances/Instance.java index 4fa289808..e04026364 100644 --- a/src/main/java/org/folio/inventory/domain/instances/Instance.java +++ b/src/main/java/org/folio/inventory/domain/instances/Instance.java @@ -26,6 +26,7 @@ import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; +import org.folio.inventory.support.JsonHelper; public class Instance { // JSON property names @@ -269,7 +270,7 @@ public JsonObject getJsonForResponse(WebContext context) { putIfNotNull(json, PUBLICATION_KEY, getPublication()); putIfNotNull(json, PUBLICATION_FREQUENCY_KEY, getPublicationFrequency()); putIfNotNull(json, PUBLICATION_RANGE_KEY, getPublicationRange()); - putIfNotNull(json, ELECTRONIC_ACCESS_KEY, getElectronicAccess()); + JsonHelper.putNotNullValues(json, ELECTRONIC_ACCESS_KEY, getElectronicAccess()); putIfNotNull(json, INSTANCE_TYPE_ID_KEY, getInstanceTypeId()); putIfNotNull(json, INSTANCE_FORMAT_IDS_KEY, getInstanceFormatIds()); putIfNotNull(json, PHYSICAL_DESCRIPTIONS_KEY, getPhysicalDescriptions()); diff --git a/src/main/java/org/folio/inventory/support/JsonHelper.java b/src/main/java/org/folio/inventory/support/JsonHelper.java index 0ec9e8635..4e198f081 100644 --- a/src/main/java/org/folio/inventory/support/JsonHelper.java +++ b/src/main/java/org/folio/inventory/support/JsonHelper.java @@ -56,8 +56,10 @@ public JsonObject getJsonFileAsJsonObject(String filePath) throws IOException { */ public static void putNotNullValues(JsonObject representation, String propertyName, Object obj) { if (obj != null && isNotBlank(propertyName)) { - if (obj instanceof Collection) { - representation.put(propertyName, new JsonArray(toNotNullList((Collection) obj))); + if (obj instanceof Collection collection) { + representation.put(propertyName, new JsonArray(toNotNullList(collection))); + } else if (obj instanceof JsonArray array) { + representation.put(propertyName, toNotNullList(array.getList())); } else { JsonObject json = JsonObject.mapFrom(obj); handleNullNestedFields(json); @@ -85,10 +87,10 @@ private static JsonObject handleNullNestedFields(JsonObject itemObject) { var value = itemObject.getValue(key); if (value == null) { keysToRemove.add(key); - } else if (value instanceof String && StringUtil.isEmpty((String) value)) { + } else if (value instanceof String str && StringUtil.isEmpty(str)) { keysToRemove.add(key); - } else if (value instanceof JsonObject) { - handleNullNestedFields((JsonObject) value); + } else if (value instanceof JsonObject object) { + handleNullNestedFields(object); } } @@ -100,8 +102,10 @@ private static List> toNotNullList(Collection collection) { return collection.stream() .filter(Objects::nonNull) .map(item -> { - if (item instanceof Collection) { - return toNotNullList((Collection) item); + if (item instanceof Collection iterable) { + return toNotNullList(iterable); + } else if (item instanceof JsonArray array) { + return toNotNullList(array.getList()); } else { var jsonItem = JsonObject.mapFrom(item); handleNullNestedFields(jsonItem); @@ -110,5 +114,4 @@ private static List> toNotNullList(Collection collection) { }) .toList(); } - } diff --git a/src/test/java/org/folio/inventory/support/JsonHelperTest.java b/src/test/java/org/folio/inventory/support/JsonHelperTest.java index d5b442b84..c7a7a9fc5 100644 --- a/src/test/java/org/folio/inventory/support/JsonHelperTest.java +++ b/src/test/java/org/folio/inventory/support/JsonHelperTest.java @@ -1,5 +1,6 @@ package org.folio.inventory.support; +import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,9 +56,13 @@ public void shouldNotIncludeNullOeEmptyValues() { var key = "key"; var rootKey = "root"; var arrayKey = "array"; + var jsonArrayKey = "jsonArray"; + var arrayListKey = "arrayList"; var value = new JsonObject(); var nestedValue = new JsonObject(); var list = new ArrayList(); + var arrayList = new ArrayList(); + var jsonArray = new JsonArray(); nestedValue.put(nutNullString, nutNullString); nestedValue.put("null", null); @@ -65,13 +70,23 @@ public void shouldNotIncludeNullOeEmptyValues() { value.put(key, nestedValue); list.add(nestedValue); list.add(null); + arrayList.add(JsonArray.of(nestedValue)); + jsonArray.add(nestedValue); putNotNullValues(representation, rootKey, value); putNotNullValues(representation, arrayKey, list); + putNotNullValues(representation, arrayListKey, arrayList); + putNotNullValues(representation, jsonArrayKey, jsonArray); var objResult = representation.getJsonObject(rootKey).getJsonObject(key); var listResult = representation.getJsonArray(arrayKey); + var arrayListResult = representation.getJsonArray(arrayListKey).getJsonArray(0).getJsonObject(0); + var jsonArrayResult = representation.getJsonArray(jsonArrayKey).getJsonObject(0); assertThat(objResult.size(), is(1)); assertThat(objResult.getValue(nutNullString), is(nutNullString)); + assertThat(arrayListResult.size(), is(1)); + assertThat(arrayListResult.getValue(nutNullString), is(nutNullString)); + assertThat(jsonArrayResult.size(), is(1)); + assertThat(jsonArrayResult.getValue(nutNullString), is(nutNullString)); assertThat(listResult.size(), is(1)); } } From fcaf425694141e3345445b43ca016eb6c33b9d84 Mon Sep 17 00:00:00 2001 From: Volodymyr_Rohach Date: Thu, 25 Apr 2024 15:16:25 +0300 Subject: [PATCH 19/24] [maven-release-plugin] prepare release v20.2.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 632ad331b..5b9bcc2d3 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.3-SNAPSHOT + 20.2.3 Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.0 + v20.2.3 From bafc0d983c7285c8e271027b00f71896853de26d Mon Sep 17 00:00:00 2001 From: Volodymyr_Rohach Date: Thu, 25 Apr 2024 15:16:26 +0300 Subject: [PATCH 20/24] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5b9bcc2d3..59e1c9ab1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.3 + 20.2.4-SNAPSHOT Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.3 + v20.2.0 From c10914e3307effd5465cc103ec92265730049993 Mon Sep 17 00:00:00 2001 From: Javokhir Abdullaev <101543142+javokhirabdullayev@users.noreply.github.com> Date: Mon, 6 May 2024 15:44:07 +0300 Subject: [PATCH 21/24] MODINV-1024 422 Unprocessable Content Error while updating Instances and Items with electronic access without URI field populated. (#720) * MODINV-1024 422 Unprocessable Content Error while updating Instances and Items with electronic access without URI field populated. * fix test * fix test * remove unused import (cherry picked from commit 9af7ad0d30e8b46565fe9e4cf76abd91930b0747) --- .../folio/inventory/support/JsonHelper.java | 3 --- .../resources/ItemRepresentationTest.java | 4 ++-- .../inventory/support/JsonHelperTest.java | 20 +++++++++---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/folio/inventory/support/JsonHelper.java b/src/main/java/org/folio/inventory/support/JsonHelper.java index 4e198f081..e46d0d373 100644 --- a/src/main/java/org/folio/inventory/support/JsonHelper.java +++ b/src/main/java/org/folio/inventory/support/JsonHelper.java @@ -2,7 +2,6 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; -import liquibase.util.StringUtil; import java.io.BufferedReader; import java.io.IOException; @@ -87,8 +86,6 @@ private static JsonObject handleNullNestedFields(JsonObject itemObject) { var value = itemObject.getValue(key); if (value == null) { keysToRemove.add(key); - } else if (value instanceof String str && StringUtil.isEmpty(str)) { - keysToRemove.add(key); } else if (value instanceof JsonObject object) { handleNullNestedFields(object); } diff --git a/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java b/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java index 380cc7d63..31e5f8b82 100644 --- a/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java +++ b/src/test/java/org/folio/inventory/resources/ItemRepresentationTest.java @@ -27,7 +27,7 @@ public void jsonContainsVersion() { } @Test - public void jsonWithoutNullOrEmptyValues() { + public void jsonWithoutNullValuesOnly() { var testValue = "https://test.com"; var electronicAccessKey = "electronicAccess"; var item = new Item(UUID.randomUUID().toString(), null, null, @@ -46,7 +46,7 @@ public void jsonWithoutNullOrEmptyValues() { var emptyElectronicAccessObject = nullElectronicAccessJson.getJsonArray(electronicAccessKey); assertThat(electronicAccessObject.size(), is(1)); - assertThat(electronicAccessObject.getJsonObject(0).fieldNames().size(), is(1)); + assertThat(electronicAccessObject.getJsonObject(0).fieldNames().size(), is(2)); assertThat(electronicAccessObject.getJsonObject(0).getValue("uri"), is(testValue)); assertThat(emptyElectronicAccessObject, is(new JsonArray())); } diff --git a/src/test/java/org/folio/inventory/support/JsonHelperTest.java b/src/test/java/org/folio/inventory/support/JsonHelperTest.java index c7a7a9fc5..60f9510b5 100644 --- a/src/test/java/org/folio/inventory/support/JsonHelperTest.java +++ b/src/test/java/org/folio/inventory/support/JsonHelperTest.java @@ -51,8 +51,8 @@ public void shouldNotIncludeIfValueIsNull() { } @Test - public void shouldNotIncludeNullOeEmptyValues() { - var nutNullString = "notNull"; + public void shouldNotIncludeOnlyNullValues() { + var notNullString = "notNull"; var key = "key"; var rootKey = "root"; var arrayKey = "array"; @@ -64,7 +64,7 @@ public void shouldNotIncludeNullOeEmptyValues() { var arrayList = new ArrayList(); var jsonArray = new JsonArray(); - nestedValue.put(nutNullString, nutNullString); + nestedValue.put(notNullString, notNullString); nestedValue.put("null", null); nestedValue.put("empty", ""); value.put(key, nestedValue); @@ -81,12 +81,12 @@ public void shouldNotIncludeNullOeEmptyValues() { var listResult = representation.getJsonArray(arrayKey); var arrayListResult = representation.getJsonArray(arrayListKey).getJsonArray(0).getJsonObject(0); var jsonArrayResult = representation.getJsonArray(jsonArrayKey).getJsonObject(0); - assertThat(objResult.size(), is(1)); - assertThat(objResult.getValue(nutNullString), is(nutNullString)); - assertThat(arrayListResult.size(), is(1)); - assertThat(arrayListResult.getValue(nutNullString), is(nutNullString)); - assertThat(jsonArrayResult.size(), is(1)); - assertThat(jsonArrayResult.getValue(nutNullString), is(nutNullString)); - assertThat(listResult.size(), is(1)); + assertThat(objResult.size(), is(2)); + assertThat(objResult.getValue(notNullString), is(notNullString)); + assertThat(arrayListResult.size(), is(2)); + assertThat(arrayListResult.getValue(notNullString), is(notNullString)); + assertThat(jsonArrayResult.size(), is(2)); + assertThat(jsonArrayResult.getValue(notNullString), is(notNullString)); + assertThat(listResult.getList().size(), is(1)); } } From 2510b9f79adf45986347bb9af8c513b76efbded1 Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Thu, 9 May 2024 13:21:20 +0300 Subject: [PATCH 22/24] update news --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index c9246bb21..704977eed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,6 @@ +## 20.2.4 2024-05-09 +* [MODINV-1024](https://folio-org.atlassian.net/browse/MODINV-1024) Fix error while updating Instances and Items with electronic access without URI field populated. + ## 20.2.3 2024-04-25 * [MODINV-1022](https://folio-org.atlassian.net/browse/MODINV-1022) Circulation History in Item record removed when Item is marked Missing/Long missing/ Unavailable From 1988def6d28b1215419a5509ca385c01df61c4f7 Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Thu, 9 May 2024 13:22:09 +0300 Subject: [PATCH 23/24] [maven-release-plugin] prepare release v20.2.4 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 59e1c9ab1..64e0c3b71 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.4-SNAPSHOT + 20.2.4 Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.0 + v20.2.4 From 8219ae18583f68755ffaf13bf1a2cda7e717455c Mon Sep 17 00:00:00 2001 From: Kateryna_Senchenko Date: Thu, 9 May 2024 13:22:10 +0300 Subject: [PATCH 24/24] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 64e0c3b71..d137cfee1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-inventory org.folio - 20.2.4 + 20.2.5-SNAPSHOT Apache License 2.0 @@ -345,7 +345,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v20.2.4 + v20.2.0