-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Upload FHIR resources #86
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
package org.smartregister.command; | ||
|
||
import org.apache.http.HttpResponse; | ||
import org.apache.http.client.methods.HttpPost; | ||
import org.apache.http.entity.StringEntity; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.json.JSONObject; | ||
import org.smartregister.domain.FctFile; | ||
import org.smartregister.fhircore_tooling.BuildConfig; | ||
import org.smartregister.util.FctUtils; | ||
import picocli.CommandLine; | ||
|
||
import java.io.*; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.text.SimpleDateFormat; | ||
import java.util.*; | ||
|
||
@CommandLine.Command(name = "publish") | ||
public class PublishFhirResourcesCommand implements Runnable{ | ||
|
||
@CommandLine.Option( | ||
names = {"-i", "--input"}, | ||
description = "path of the file or folder to publish") | ||
String projectFolder; | ||
|
||
@CommandLine.Option( | ||
names = {"-bu", "--fhir-base-url"}, | ||
description = "fhir server base url") | ||
String fhirBaseUrl; | ||
|
||
@CommandLine.Option( | ||
names = {"-at", "--access-token"}, | ||
description = "access token for fhir server") | ||
String accessToken; | ||
|
||
@CommandLine.Option( | ||
names = {"-e", "--env"}, | ||
description = "path to env.properties file") | ||
String propertiesFile; | ||
|
||
@Override | ||
public void run() { | ||
long start = System.currentTimeMillis(); | ||
if(propertiesFile != null && !propertiesFile.isBlank()){ | ||
try(InputStream inputProperties = new FileInputStream(propertiesFile)){ | ||
Properties properties = new Properties(); | ||
properties.load(inputProperties); | ||
setProperties(properties); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
try { | ||
publishResources(); | ||
stateManagement(); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
FctUtils.printCompletedInDuration(start); | ||
} | ||
|
||
void setProperties(Properties properties){ | ||
if (projectFolder == null || projectFolder.isBlank()){ | ||
if (properties.getProperty("projectFolder") != null){ | ||
projectFolder = properties.getProperty("projectFolder"); | ||
} else { | ||
throw new NullPointerException("The projectFolder is missing"); | ||
} | ||
} | ||
if (fhirBaseUrl == null || fhirBaseUrl.isBlank()){ | ||
if (properties.getProperty("fhirBaseUrl") != null){ | ||
fhirBaseUrl = properties.getProperty("fhirBaseUrl"); | ||
} else { | ||
throw new NullPointerException("The fhirBaseUrl is missing"); | ||
} | ||
} | ||
if (accessToken == null || accessToken.isBlank()){ | ||
if (properties.getProperty("accessToken") != null){ | ||
accessToken = properties.getProperty("accessToken"); | ||
} else { | ||
throw new NullPointerException("The accessToken is missing"); | ||
} | ||
} | ||
} | ||
|
||
void publishResources() throws IOException { | ||
ArrayList<String> resourceFiles = getResourceFiles(projectFolder); | ||
ArrayList<JSONObject> resourceObjects = new ArrayList<>(); | ||
for(String f: resourceFiles){ | ||
FctFile inputFile = FctUtils.readFile(f); | ||
// TODO check if file contains valid fhir resource | ||
JSONObject resourceObject = buildResourceObject(inputFile); | ||
resourceObjects.add(resourceObject); | ||
} | ||
|
||
// build the bundle | ||
JSONObject bundle = new JSONObject(); | ||
bundle.put("resourceType", "Bundle"); | ||
bundle.put("type", "transaction"); | ||
bundle.put("entry",resourceObjects); | ||
FctUtils.printToConsole("Full Payload to POST: "); | ||
FctUtils.printToConsole(bundle.toString()); | ||
|
||
postRequest(bundle.toString(), accessToken); | ||
} | ||
|
||
ArrayList<String> getResourceFiles(String pathToFolder) throws IOException { | ||
ArrayList<String> filesArray = new ArrayList<>(); | ||
Path projectPath = Paths.get(pathToFolder); | ||
if (Files.isDirectory(projectPath)){ | ||
Files.walk(projectPath).forEach(path -> getFiles(filesArray, path.toFile())); | ||
} else if (Files.isRegularFile(projectPath)) { | ||
filesArray.add(pathToFolder); | ||
} | ||
return filesArray; | ||
} | ||
|
||
void getFiles(ArrayList<String> filesArray, File file){ | ||
if (file.isFile()) { | ||
filesArray.add(file.getAbsolutePath()); | ||
} | ||
} | ||
|
||
JSONObject buildResourceObject(FctFile inputFile){ | ||
JSONObject resource = new JSONObject(inputFile.getContent()); | ||
String resourceType = null; | ||
String resourceID; | ||
if(resource.has("resourceType")) { | ||
resourceType = resource.getString("resourceType"); | ||
} | ||
if(resource.has("id")){ | ||
resourceID = resource.getString("id"); | ||
} else { | ||
resourceID = UUID.randomUUID().toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. generate a random UUID incase this is not provided There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to seed Java's random lib when before calling this? I don't remember There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, it works as is |
||
} | ||
|
||
JSONObject request = new JSONObject(); | ||
request.put("method", "PUT"); | ||
request.put("url", resourceType + "/" + resourceID ); | ||
|
||
ArrayList<JSONObject> tags = new ArrayList<>(); | ||
JSONObject version = new JSONObject(); | ||
version.put("system", "https://smartregister.org/fct-release-version"); | ||
version.put("code", BuildConfig.RELEASE_VERSION); | ||
tags.add(version); | ||
|
||
JSONObject meta = new JSONObject(); | ||
meta.put("tag", tags); | ||
resource.put("meta", meta); | ||
|
||
JSONObject object = new JSONObject(); | ||
object.put("resource", resource); | ||
object.put("request", request); | ||
|
||
return object; | ||
} | ||
|
||
void postRequest(String payload, String accessToken) throws IOException { | ||
CloseableHttpClient httpClient = HttpClients.createDefault(); | ||
HttpPost httpPost = new HttpPost(fhirBaseUrl); | ||
httpPost.setHeader("Content-Type", "application/fhir+json"); | ||
httpPost.setHeader("Authorization", "Bearer " + accessToken); | ||
httpPost.setEntity(new StringEntity(payload)); | ||
HttpResponse response = httpClient.execute(httpPost); | ||
|
||
FctUtils.printToConsole("Response Status: " + response.getStatusLine().getStatusCode()); | ||
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); | ||
String inputLine; | ||
StringBuilder responseString = new StringBuilder(); | ||
|
||
while ((inputLine = reader.readLine()) != null) { | ||
responseString.append(inputLine); | ||
} | ||
reader.close(); | ||
FctUtils.printToConsole("Response Content: " + responseString); | ||
} | ||
|
||
void stateManagement() throws IOException { | ||
String pathToManifest; | ||
if(Files.isDirectory(Paths.get(projectFolder))){ | ||
pathToManifest = projectFolder + "/.efsity/state.json"; | ||
} else { | ||
pathToManifest = getProjectFolder(projectFolder); | ||
} | ||
File manifestFile = new File(pathToManifest); | ||
|
||
// Create folder if it does not exist | ||
if (Files.notExists(Paths.get(pathToManifest))){ | ||
if(manifestFile.getParentFile().mkdirs()){ | ||
if(manifestFile.createNewFile()){ | ||
FctUtils.printToConsole("Manifest file created successfully"); | ||
} | ||
} | ||
} | ||
|
||
// Set initial content | ||
String initialContent; | ||
if (manifestFile.length() != 0){ | ||
initialContent = FctUtils.readFile(pathToManifest).getContent(); | ||
} else { | ||
initialContent = "[]"; | ||
} | ||
|
||
JSONObject currentState = new JSONObject(); | ||
currentState.put("fctVersion", BuildConfig.RELEASE_VERSION); | ||
currentState.put("url", fhirBaseUrl); | ||
currentState.put("updated", updatedAt()); | ||
String finalString; | ||
if (manifestFile.length() != 0){ | ||
finalString = initialContent.substring(0, initialContent.length() - 2) + ",\n" + currentState + "]"; | ||
} else { | ||
finalString = initialContent.substring(0, initialContent.length() - 1) + currentState + "]"; | ||
} | ||
|
||
FileWriter writer = new FileWriter(pathToManifest); | ||
writer.write(finalString); | ||
writer.flush(); | ||
writer.close(); | ||
} | ||
|
||
String updatedAt(){ | ||
Date date = new Date(System.currentTimeMillis()); | ||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); | ||
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); | ||
return sdf.format(date); | ||
} | ||
|
||
// This function assumes the .efsity folder already exists in the project/parent folder | ||
// and simply tries to find it | ||
String getProjectFolder(String projectFolder){ | ||
File resourceFile = new File(projectFolder); | ||
File parentFolder = resourceFile.getParentFile(); | ||
boolean check = new File(parentFolder, ".efsity").exists(); | ||
if (!check){ | ||
return getProjectFolder(parentFolder.toString()); | ||
} | ||
return parentFolder.toString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package org.smartregister.command; | ||
|
||
import org.json.JSONObject; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.io.TempDir; | ||
import org.smartregister.domain.FctFile; | ||
import org.smartregister.util.FctUtils; | ||
|
||
import java.io.FileWriter; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
public class PublishFhirResourcesCommandTest { | ||
|
||
private PublishFhirResourcesCommand publishFhirResourcesCommand; | ||
|
||
@TempDir | ||
static Path tempDirectory; | ||
|
||
@BeforeEach | ||
void setUp(){ | ||
publishFhirResourcesCommand = new PublishFhirResourcesCommand(); | ||
} | ||
|
||
@Test | ||
void testGetResourceFiles() throws IOException { | ||
// add folders and file to form sample project structure | ||
Path testFolder = Files.createDirectory(tempDirectory.resolve("testFileFolder")); | ||
Path projectFolder = Files.createDirectory(testFolder.resolve("testProject")); | ||
|
||
// create 3 folders in projectFolder | ||
Path questionnaireFolder = Files.createDirectory(projectFolder.resolve("questionnaires")); | ||
Path plansFolder = Files.createDirectory(projectFolder.resolve("plan_definitions")); | ||
Path structureMapsFolder = Files.createDirectory(projectFolder.resolve("structureMaps")); | ||
|
||
// create a file in each of the folders above | ||
Path questionnaireFile = Files.createFile(questionnaireFolder.resolve("patient_registration.json")); | ||
Path planFile = Files.createFile(plansFolder.resolve("anc_visit.json")); | ||
Path structureMapFile = Files.createFile(structureMapsFolder.resolve("pregnancy_screening.json")); | ||
|
||
// get files in the folder | ||
ArrayList<String> resourceFiles = publishFhirResourcesCommand.getResourceFiles(projectFolder.toString()); | ||
|
||
assertEquals(3, resourceFiles.size()); | ||
assertTrue(resourceFiles.contains(questionnaireFile.toString())); | ||
assertTrue(resourceFiles.contains(planFile.toString())); | ||
assertTrue(resourceFiles.contains(structureMapFile.toString())); | ||
} | ||
|
||
@Test | ||
void testBuildResourceObject() throws IOException { | ||
Path testFolder = Files.createDirectory(tempDirectory.resolve("testObjectFolder")); | ||
Path resourceFile = Files.createFile(testFolder.resolve("group.json")); | ||
|
||
String sampleResource = "{\n" + | ||
" \"resourceType\": \"Group\",\n" + | ||
" \"id\": \"548060c9-8e9b-4b0d-88e7-925e9348fdae\",\n" + | ||
" \"identifier\": [\n" + | ||
" {\n" + | ||
" \"use\": \"official\",\n" + | ||
" \"value\": \"548060c9-8e9b-4b0d-88e7-925e9348fdae\"\n" + | ||
" }\n" + | ||
" ],\n" + | ||
" \"active\": false,\n" + | ||
" \"name\": \"Test Group\"\n" + | ||
"}"; | ||
FileWriter writer = new FileWriter(String.valueOf(resourceFile)); | ||
writer.write(sampleResource); | ||
writer.flush(); | ||
writer.close(); | ||
|
||
FctFile testFile = FctUtils.readFile(resourceFile.toString()); | ||
JSONObject resourceObject = publishFhirResourcesCommand.buildResourceObject(testFile); | ||
|
||
// assert that object has request | ||
assertEquals( | ||
"{\"method\":\"PUT\",\"url\":\"Group/548060c9-8e9b-4b0d-88e7-925e9348fdae\"}", | ||
resourceObject.get("request").toString()); | ||
|
||
// assert object has meta with version tag | ||
JSONObject resource = (JSONObject) resourceObject.get("resource"); | ||
assertTrue(resource.get("meta").toString() | ||
.contains("{\"tag\":[{\"system\":\"https://smartregister.org/fct-release-version\",\"code\":\"")); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking of adding a prompt here to let the publisher continue or stop once they see if their file is valid or not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I think we should default stop if any file is not valid and fail everything before upload start if any is not valid, but you can pass a flag on invocation that's like "do not validate"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, created separate issue for this