Skip to content

Commit

Permalink
Maintain current config files on failure (#214)
Browse files Browse the repository at this point in the history
* init multifactor authentication setup

* Revert "init multifactor authentication setup"

This reverts commit 826f31a.

* put translations in the same file if -tf is not given

* deep copy config folder

* copy over temp configs in happy path

* create temp file when config points to single resource file

* delete tmp files after translation

* fix failing test

* add test

* test behavior for malformed json

* change how properties file is read

* put translations in the same file if -tf is not given

* deep copy config folder

* Add i18N support for new config fields

* Update TransformSupportServices class to match FHIRCore

* Refactor Versioning, remove Duplication

* Release Version 2.3.6 🔖

* copy over temp configs in happy path

* create temp file when config points to single resource file

* delete tmp files after translation

* fix failing test

* add test

* test behavior for malformed json

* Handle deletion of inventory identifiers properly (#219)

* Add product material number indentifier (#220)

* Refactor i17N fix encoding bug 🐛

* Create List resource to reference product resources (#221)

* Create List resource referencing Group and Binary resources created by product import

* Add List resource json payload

* Allow auto updating of the List resource

* Add SM Tool readme file (#223)

* change how properties file is read

* Initial Commit (#224)

---------

Co-authored-by: Martin Ndegwa <mndegwa@ona.io>
Co-authored-by: Wambere <janette@wambere.com>
Co-authored-by: Sharon Akinyi <79141719+sharon2719@users.noreply.github.com>
Co-authored-by: Francis Odhiambo <4540684+f-odhiambo@users.noreply.github.com>
  • Loading branch information
5 people authored Jul 16, 2024
1 parent f931d85 commit 8d9db17
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 43 deletions.
171 changes: 128 additions & 43 deletions efsity/src/main/java/org/smartregister/command/TranslateCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
Expand Down Expand Up @@ -58,6 +59,9 @@ public class TranslateCommand implements Runnable {

private static String url = "http://hl7.org/fhir/StructureDefinition/translation";

Path tempsConfig = null;
Path tempFilePath = null;

@Override
public void run() {
if (!Arrays.asList(modes).contains(mode)) {
Expand All @@ -76,27 +80,27 @@ public void run() {
FctUtils.printInfo(String.format("Input file \u001b[35m%s\u001b[0m", resourceFile));

try {
Path translationsDirectoryPath = inputFilePath.getParent().resolve("translations");

if (Objects.equals(extractionType, "configs")) {
tempsConfig = Files.createTempDirectory("configs");
} else tempsConfig = null;
if (!Files.exists(translationsDirectoryPath))
Files.createDirectories(translationsDirectoryPath);
if (translationFile == null) {
translationFile = translationsDirectoryPath + "/strings_default.properties";
}
// Check if the input path is a directory or a JSON file
if (Files.isDirectory(inputFilePath)) {

if (Objects.equals(extractionType, "configs") || inputFilePath.endsWith("configs")) {
extractionType = "configs";
Set<String> targetFields = FCTConstants.configTranslatables;

if (translationFile == null) {
translationFile =
inputFilePath.resolve("translations/strings_config.properties").toString();
}
copyDirectoryContent(inputFilePath, tempsConfig);
extractContent(translationFile, inputFilePath, targetFields, extractionType);
} else if (Objects.equals(extractionType, "fhirContent")
|| inputFilePath.endsWith("fhir_content")) {
extractionType = "fhirContent";
Set<String> targetFields = FCTConstants.questionnaireTranslatables;

if (translationFile == null) {
translationFile =
inputFilePath.resolve("translations/strings_default.properties").toString();
}
if (!inputFilePath.endsWith("questionnaires")) {
inputFilePath = inputFilePath.resolve("questionnaires");
}
Expand All @@ -109,13 +113,7 @@ public void run() {
if (Files.exists(configsPath) && Files.isDirectory(configsPath)) {
extractionType = "configs";
Set<String> targetFields = FCTConstants.configTranslatables;
String configsTranslationFile = null;
configsTranslationFile =
Objects.requireNonNullElseGet(
translationFile,
() ->
configsPath.resolve("translations/strings_config.properties").toString());
extractContent(configsTranslationFile, configsPath, targetFields, extractionType);
extractContent(translationFile, configsPath, targetFields, extractionType);
} else {
FctUtils.printWarning("`configs` directory not found in directory");
}
Expand All @@ -125,16 +123,8 @@ public void run() {
&& Files.isDirectory(questionnairePath)) {
extractionType = "fhirContent";
Set<String> targetFields = FCTConstants.questionnaireTranslatables;
String contentTranslationFile = null;
contentTranslationFile =
Objects.requireNonNullElseGet(
translationFile,
() ->
fhirContentPath
.resolve("translations/strings_default.properties")
.toString());
extractContent(
contentTranslationFile, questionnairePath, targetFields, extractionType);

extractContent(translationFile, questionnairePath, targetFields, extractionType);
} else {
FctUtils.printWarning(
"`fhir_content` or `fhir_content/questionnaires` directory not found in directory");
Expand Down Expand Up @@ -349,32 +339,47 @@ private static JsonNode createExtensionNode(String locale, String translation) {
return extensionArray;
}

private static void extractContent(
private void extractContent(
String translationFile, Path inputFilePath, Set<String> targetFields, String extractionType)
throws IOException, NoSuchAlgorithmException {
Map<String, String> textToHash = new HashMap<>();
Path propertiesFilePath = Paths.get(translationFile);

if (Files.isRegularFile(inputFilePath)
&& inputFilePath.toString().toLowerCase(Locale.ENGLISH).endsWith(".json")) {

if (Objects.equals(extractionType, "configs")) {
String configFileSubDirectory =
inputFilePath.subpath(2, inputFilePath.getNameCount() - 1).toString();
try {
Path tempConfigSubDirectory = tempsConfig.resolve(configFileSubDirectory);
if (!Files.exists(tempConfigSubDirectory)) {
Files.createDirectories(tempConfigSubDirectory);
tempFilePath = tempConfigSubDirectory.resolve(inputFilePath.getFileName());
// copy over content
Files.copy(inputFilePath, tempFilePath, StandardCopyOption.REPLACE_EXISTING);
}

} catch (IOException e) {
throw new RuntimeException("Error creating temp file " + e);
}
// Extract and replace target fields with hashed values
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode =
objectMapper.readTree(Files.newBufferedReader(inputFilePath, StandardCharsets.UTF_8));
FctUtils.printInfo(
String.format(
"Extracting config file \u001b[35m%s\u001b[0m", inputFilePath.toString()));
replaceTargetFieldsWithHashedValues(rootNode, targetFields, textToHash, inputFilePath);

replaceTargetFieldsWithHashedValues(
rootNode, targetFields, textToHash, inputFilePath, tempsConfig);
} else {
// For other types (content/questionnaire), extract as usual
processJsonFile(inputFilePath, textToHash, targetFields);
}
} else if (Files.isDirectory(inputFilePath)) {
// Handle the case where inputFilePath is a directory (folders may contain multiple JSON
// files)
Files.walk(inputFilePath)

Files.walk(tempsConfig)
.filter(Files::isRegularFile)
.filter(file -> file.toString().endsWith(".json"))
.forEach(
Expand All @@ -389,25 +394,39 @@ private static void extractContent(
FctUtils.printInfo(
String.format(
"Extracting config file \u001b[35m%s\u001b[0m", file.toString()));
replaceTargetFieldsWithHashedValues(rootNode, targetFields, textToHash, file);

replaceTargetFieldsWithHashedValues(
rootNode, targetFields, textToHash, file, tempsConfig);
} else {
// For other types (content/questionnaire), extract as usual
processJsonFile(file, textToHash, targetFields);
}
} catch (IOException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
deleteDirectoryRecursively(tempsConfig);
throw new RuntimeException(
"Error while reading file " + file.getFileName() + " " + e);
}
});
} else {
throw new RuntimeException("Provide a valid `resourceFile` directory or file.");
}

// Copy over translations from temp
if (extractionType.equals("configs")) {
if (Files.isDirectory(inputFilePath)) copyDirectoryContent(tempsConfig, inputFilePath);
else {
assert tempFilePath != null;
Files.copy(tempFilePath, inputFilePath, StandardCopyOption.REPLACE_EXISTING);
}
}

Properties existingProperties = FctUtils.readPropertiesFile(propertiesFilePath.toString());

// Merge existing properties with new properties
existingProperties.putAll(textToHash);
writePropertiesFile(existingProperties, translationFile);
FctUtils.printInfo(String.format("Translation file\u001b[36m %s \u001b[0m", translationFile));
if (tempsConfig != null) deleteDirectoryRecursively(tempsConfig);
}

private static void processJsonFile(
Expand All @@ -421,8 +440,12 @@ private static void processJsonFile(
}

private static void replaceTargetFieldsWithHashedValues(
JsonNode node, Set<String> targetFields, Map<String, String> textToHash, Path filePath)
throws NoSuchAlgorithmException {
JsonNode node,
Set<String> targetFields,
Map<String, String> textToHash,
Path filePath,
Path tempConfigsDir)
throws NoSuchAlgorithmException, IOException {

if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
Expand All @@ -444,7 +467,8 @@ private static void replaceTargetFieldsWithHashedValues(
JsonNode fieldValue = field.getValue();
if (fieldValue.isObject() || fieldValue.isArray()) {
// Recursively update nested objects or arrays
replaceTargetFieldsWithHashedValues(fieldValue, targetFields, textToHash, filePath);
replaceTargetFieldsWithHashedValues(
fieldValue, targetFields, textToHash, filePath, tempConfigsDir);
}
}
} else if (node.isArray()) {
Expand All @@ -453,18 +477,20 @@ private static void replaceTargetFieldsWithHashedValues(
JsonNode arrayElement = arrayNode.get(i);
if (arrayElement.isObject() || arrayElement.isArray()) {
// Recursively update nested objects or arrays
replaceTargetFieldsWithHashedValues(arrayElement, targetFields, textToHash, filePath);
replaceTargetFieldsWithHashedValues(
arrayElement, targetFields, textToHash, filePath, tempConfigsDir);
}
}
}

// Write the updated JSON back to the file
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) {
String configFileSubDirectory = filePath.subpath(2, filePath.getNameCount()).toString();

Path tempFilePath = tempConfigsDir.resolve(configFileSubDirectory);
// Write the updated JSON to temp file
try (BufferedWriter writer = Files.newBufferedWriter(tempFilePath, StandardCharsets.UTF_8)) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.writeValue(writer, node);
} catch (IOException e) {
throw new RuntimeException("Failed to write the updated JSON to file.", e);
}
}

Expand Down Expand Up @@ -524,4 +550,63 @@ private static void writePropertiesFile(Properties properties, String filePath)
properties.store(output, null);
}
}

public void copyDirectoryContent(Path sourceDir, Path destinationDir) {
try {
Files.walkFileTree(
sourceDir,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Path targetDir = destinationDir.resolve(sourceDir.relativize(dir));
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {

Files.copy(
file,
destinationDir.resolve(sourceDir.relativize(file)),
StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void deleteDirectoryRecursively(Path dirPath) {

try {
// Delete the directory and its contents recursively
Files.walkFileTree(
dirPath,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});

System.out.println("Directory and its contents deleted successfully: " + dirPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.smartregister.util.FctUtils;

public class TranslateCommandTest {
Expand Down Expand Up @@ -67,6 +68,41 @@ public void testRunExtract() throws IOException {
tempDefaultPropertiesPath.toFile().delete();
}

@Test
public void testRunExtractConfigWithCleanConfigsFolderRunsSuccessfully() throws IOException {
Path cleanConfigsFolder = Paths.get("src/test/resources/clean_configs_folder");
TranslateCommand translateCommandForCopyingTemp = new TranslateCommand();
Path backupFolder = Files.createTempDirectory("temp_back_up_dir");
translateCommandForCopyingTemp.copyDirectoryContent(cleanConfigsFolder, backupFolder);
TranslateCommand translateCommandSpy = Mockito.spy(translateCommand);
translateCommandSpy.mode = "extract";
translateCommandSpy.extractionType = "configs";
translateCommandSpy.resourceFile = cleanConfigsFolder.toString();
translateCommandSpy.run();
Mockito.verify(translateCommandSpy, Mockito.atLeast(2))
.copyDirectoryContent(Mockito.any(), Mockito.any());
Mockito.verify(translateCommandSpy, Mockito.atLeast(1))
.deleteDirectoryRecursively(Mockito.any());
// restore clean_configs_folder
translateCommandForCopyingTemp.copyDirectoryContent(backupFolder, cleanConfigsFolder);
}

@Test
public void testRunExtractConfigWithDirtyConfigsFolderDeletesTempFileOnFailure()
throws RuntimeException {
Path cleanConfigsFolder = Paths.get("src/test/resources/dirty_configs_folder");
TranslateCommand translateCommandSpy = Mockito.spy(translateCommand);
translateCommandSpy.mode = "extract";
translateCommandSpy.extractionType = "configs";
translateCommandSpy.resourceFile = cleanConfigsFolder.toString();

assertThrows(RuntimeException.class, translateCommandSpy::run);
Mockito.verify(translateCommandSpy, Mockito.atLeast(1))
.copyDirectoryContent(Mockito.any(), Mockito.any());
Mockito.verify(translateCommandSpy, Mockito.atLeast(1))
.deleteDirectoryRecursively(Mockito.any());
}

@Test
public void testRunMerge() throws IOException {
Path rawQuestionnairePath = Paths.get("src/test/resources/raw_questionnaire.json");
Expand Down
Loading

0 comments on commit 8d9db17

Please sign in to comment.