Skip to content

Commit

Permalink
refactor(reorder fields): keep order of MARC fields while Creating/De…
Browse files Browse the repository at this point in the history
…riving/Editing MARC records. (#858)

* refactor(reorder fields):

reorder content fields

add unit test for reorderMarcRecordFields method

return target content instead of null value

create constant FIELDS

Closes: MODQM-407
  • Loading branch information
TsaghikKhachatryan authored Mar 19, 2024
1 parent eece47c commit dbc3de7
Show file tree
Hide file tree
Showing 4 changed files with 472 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package org.folio.services.afterprocessing;

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 com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
Expand Down Expand Up @@ -28,9 +32,14 @@
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer;

Expand All @@ -50,6 +59,8 @@ public final class AdditionalFieldsUtil {

private final static CacheLoader<Object, org.marc4j.marc.Record> parsedRecordContentCacheLoader;
private final static LoadingCache<Object, org.marc4j.marc.Record> 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.
Expand Down Expand Up @@ -149,8 +160,9 @@ public static boolean addFieldToMarcRecord(Record record, String field, char sub
String parsedContentString = new JsonObject(os.toString()).encode();
parsedRecordContentCache.invalidate(record.getParsedRecord().getContent());
// save parsed content string to cache then set it on the record
parsedRecordContentCache.put(parsedContentString, marcRecord);
record.setParsedRecord(record.getParsedRecord().withContent(parsedContentString));
var content = reorderMarcRecordFields(record.getParsedRecord().getContent().toString(), parsedContentString);
parsedRecordContentCache.put(content, marcRecord);
record.setParsedRecord(record.getParsedRecord().withContent(content));
result = true;
}
}
Expand All @@ -160,6 +172,59 @@ public static boolean addFieldToMarcRecord(Record record, String field, char sub
return result;
}

private static String reorderMarcRecordFields(String sourceContent, String targetContent) {
try {
var parsedContent = objectMapper.readTree(targetContent);
var fieldsArrayNode = (ArrayNode) parsedContent.path(FIELDS);

Map<String, Queue<JsonNode>> jsonNodesByTag = groupNodesByTag(fieldsArrayNode);

List<String> sourceFields = getSourceFields(sourceContent);

var rearrangedArray = objectMapper.createArrayNode();
for (String tag : sourceFields) {
Queue<JsonNode> 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 List<String> getSourceFields(String source) {
List<String> sourceFields = new ArrayList<>();
try {
var sourceJson = objectMapper.readTree(source);
var fieldsNode = sourceJson.get(FIELDS);
for (JsonNode fieldNode : fieldsNode) {
String tag = fieldNode.fieldNames().next();
sourceFields.add(tag);
}
} catch (Exception e) {
LOGGER.error("An error occurred while parsing source JSON: {}", e.getMessage(), e);
}
return sourceFields;
}

private static Map<String, Queue<JsonNode>> groupNodesByTag(ArrayNode fieldsArrayNode) {
Map<String, Queue<JsonNode>> jsonNodesByTag = new HashMap<>();
fieldsArrayNode.forEach(node -> {
String tag = node.fieldNames().next();
jsonNodesByTag.computeIfAbsent(tag, k -> new LinkedList<>()).add(node);
});
return jsonNodesByTag;
}

/**
* Adds new controlled field to marc record
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.folio;

import io.vertx.core.json.JsonObject;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

import static org.folio.services.afterprocessing.AdditionalFieldsUtil.TAG_999;

/**
* Util class contains helper methods for unit testing needs
Expand All @@ -13,4 +17,34 @@ public final class TestUtil {
public static String readFileFromPath(String path) throws IOException {
return new String(FileUtils.readFileToByteArray(new File(path)));
}

public static boolean recordsHaveSameOrder(String baseContent, String newContent) {
var baseJson = new JsonObject(baseContent);
var newJson = new JsonObject(newContent);

var baseFields = baseJson.getJsonArray("fields");
var newFields = newJson.getJsonArray("fields");

for (Object newFieldObject : newFields) {
var newField = (JsonObject) newFieldObject;
if (newField.containsKey(TAG_999)) {
continue;
}
boolean foundMatchingField = false;
for (Object baseFieldObject : baseFields) {
var baseField = (JsonObject) baseFieldObject;
if (baseField.containsKey(TAG_999)) {
continue;
}
if (Objects.equals(baseField, newField)) {
foundMatchingField = true;
break;
}
}
if (!foundMatchingField) {
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Map;
import java.util.UUID;

import static org.folio.TestUtil.recordsHaveSameOrder;
import static org.folio.services.afterprocessing.AdditionalFieldsUtil.INDICATOR;
import static org.folio.services.afterprocessing.AdditionalFieldsUtil.SUBFIELD_I;
import static org.folio.services.afterprocessing.AdditionalFieldsUtil.SUBFIELD_S;
Expand All @@ -38,11 +39,13 @@
import static org.folio.services.afterprocessing.AdditionalFieldsUtil.getCacheStats;
import static org.folio.services.afterprocessing.AdditionalFieldsUtil.getValue;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertTrue;

@RunWith(BlockJUnit4ClassRunner.class)
public class AdditionalFieldsUtilTest {

private static final String PARSED_RECORD_PATH = "src/test/resources/org/folio/services/afterprocessing/parsedRecord.json";
private static final String REORDERED_PARSED_RECORD_PATH = "src/test/resources/org/folio/services/afterprocessing/reorderedParsedRecord.json";

@BeforeClass
public static void beforeClass() {
Expand Down Expand Up @@ -106,6 +109,39 @@ public void shouldAddInstanceIdSubfield() throws IOException {
Assert.assertEquals(2, totalFieldsCount);
}

@Test
public void shouldReorderParsedRecordContentAfterAddingField() throws IOException {
// given
String instanceId = UUID.randomUUID().toString();
String parsedRecordContent = TestUtil.readFileFromPath(REORDERED_PARSED_RECORD_PATH);
ParsedRecord parsedRecord = new ParsedRecord();
String leader = new JsonObject(parsedRecordContent).getString("leader");
parsedRecord.setContent(parsedRecordContent);
Record record = new Record().withId(UUID.randomUUID().toString()).withParsedRecord(parsedRecord);
var baseRecord = record.getParsedRecord().getContent().toString();
// when
boolean added = addFieldToMarcRecord(record, TAG_999, SUBFIELD_I, instanceId);
// then
assertTrue(recordsHaveSameOrder(baseRecord, record.getParsedRecord().getContent().toString()));
assertTrue(added);
JsonObject content = new JsonObject(record.getParsedRecord().getContent().toString());
JsonArray fields = content.getJsonArray("fields");
String newLeader = content.getString("leader");
Assert.assertNotEquals(leader, newLeader);
Assert.assertFalse(fields.isEmpty());
boolean existsNewField = false;
for (int i = 0; i < fields.size(); i++) {
JsonObject targetField = fields.getJsonObject(i);
if (targetField.containsKey(TAG_999)) {
existsNewField = true;
String currentTag = fields.getJsonObject(i-1).stream().map(Map.Entry::getKey).findFirst().get();
String nextTag = fields.getJsonObject(i).stream().map(Map.Entry::getKey).findFirst().get();
Assert.assertThat(currentTag, lessThanOrEqualTo(nextTag));
}
}
assertTrue(existsNewField);
}

@Test
public void shouldNotAddInstanceIdSubfieldIfNoParsedRecordContent() {
// given
Expand Down
Loading

0 comments on commit dbc3de7

Please sign in to comment.