Skip to content
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

Enable resources to be sent to a generic FHIR store using the hapi client #30

Merged
merged 7 commits into from Nov 15, 2020

Conversation

pmanko
Copy link
Collaborator

@pmanko pmanko commented Oct 7, 2020

Closes #25 and #29.

TESTED

  1. Batch mode using docker-compose
  2. Atomfeed-streaming mode using the following command: mvn exec:java -pl streaming-atomfeed -Dexec.mainClass=org.openmrs.analytics.FhirStreaming -Dexec.args="http://18.158.139.243:8091/openmrs admin/Admin123 http://18.158.139.243:8098/hapi-fhir-jpaserver/fhir"

Some fixes based on these tests were put into this PR, while other will be added as issues.

See: https://github.com/pmanko/openmrs-fhir-analytics/tree/25-hapi-client-to-sink-endtoend

@pmanko pmanko changed the title Enable resources to be sent to a generic FHIR store using the hapi client Enable resources to be sent to a generic FHIR store using the hapi client for #25 #29 Oct 23, 2020
@pmanko pmanko changed the title Enable resources to be sent to a generic FHIR store using the hapi client for #25 #29 Enable resources to be sent to a generic FHIR store using the hapi client Oct 23, 2020
@pmanko pmanko self-assigned this Oct 26, 2020
Comment on lines 128 to 130
return new GcpStoreUtil(fhirStoreUrl, fhirContext);
else
return new FhirStoreUtil(fhirStoreUrl, fhirContext);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructors here are defined to take an IGenericClient rather than FhirContext, so this should likely be something like:

return new FhirStoreUtil(fhirStoreUrl, fhirContext.newRestfulGenericClient());

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I tend to think that if ... else should always be blocks:

if (condition) {

} else {

}

This makes it easier if additional actions need to be taken.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructors here are defined to take an IGenericClient rather than FhirContext, so this should likely be something like:

return new FhirStoreUtil(fhirStoreUrl, fhirContext.newRestfulGenericClient());

Yeah, I accidentally committed in the middle of refactoring -_- thanks!

@@ -130,7 +138,7 @@ static OpenmrsUtil createOpenmrsUtil(String sourceUrl, String sourceUser, String
throws CannotProvideCoderException {
FhirSearchUtil fhirSearchUtil = createFhirSearchUtil(options, fhirContext);
// TODO remove fhirContext from fhirStoreUtil.
FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(options.getGcpFhirStore(), fhirContext);
FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(options.getFhirStoreUrl(), fhirContext);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(options.getFhirStoreUrl(), fhirContext);
FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(options.getFhirStoreUrl(), fhirContext.newRestfulGenericClient());

@@ -194,15 +202,15 @@ public void Setup() {
fhirContext = FhirContext.forDstu3();
fhirStoreUtil = new FhirStoreUtil(fhirStoreUrl, fhirContext);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fhirStoreUtil = new FhirStoreUtil(fhirStoreUrl, fhirContext);
fhirStoreUtil = new FhirStoreUtil(fhirStoreUrl, fhirContext.newRestfulGenericClient());

@pmanko pmanko force-pushed the 25-hapi-client-to-sink branch 2 times, most recently from d5f2565 to c17997b Compare October 28, 2020 00:01
@mozzy11
Copy link
Collaborator

mozzy11 commented Oct 28, 2020

@pmanko Thanks for the great work.
do we need to entirely get rid of the http utils ??

would think it would be useful at some point dealing with non FHIR data .

What do you think

Copy link
Collaborator

@bashir2 bashir2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @pmanko for this change; I like the unified interface of FhirStoreUtil. Have you tested your changes on a GCP FHIR store too?

options.getPassword(), fhirContext));
}

static FhirStoreUtil createFhirStoreUtil(String fhirStoreUrl, FhirContext fhirContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this functionality is needed elsewhere too (e.g., in the streaming pipelines), so here is a suggestion: Create a factory method somewhere under common where internally it uses the pattern matching logic of matchesGcpPattern but do not expose the metchesGcpPattern function publicly.

BTW, please update all usages of FhirStoreUtil in the code; I am thinking that the constructor for FhirStoreUtil should be private and instances of that should only be created through the factory method, WDYT?

private OpenmrsUtil openmrsUtil;

FhirSearchUtil(OpenmrsUtil openmrsUtil) {
FhirSearchUtil(FhirStoreUtil fhirStoreUtil, OpenmrsUtil openmrsUtil) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change; the dependency between FhirSearchUtil and FhirStoreUtil was intentionally removed in this commit. The idea is that, FhirStoreUtil is any functionality for uploading data to a FHIR-store and the only reason we needed FhirStoreUtil here was uploadBundleToCloud for which the right place is actually FhirStoreUtil itself.

@@ -79,4 +84,12 @@ public String findBaseSearchUrl(Bundle searchBundle) {
}
}

public MethodOutcome uploadBundleToCloud(Bundle bundle) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said above, this was intentionally moved to FhirStoreUtil, let's keep it that way.

@@ -192,17 +201,17 @@ static OpenmrsUtil createOpenmrsUtil(String sourceUrl, String sourceUser, String
@Setup
public void Setup() {
fhirContext = FhirContext.forDstu3();
fhirStoreUtil = new FhirStoreUtil(fhirStoreUrl, fhirContext);
fhirStoreUtil = new FhirStoreUtil(fhirStoreUrl, fhirContext.getRestfulClientFactory());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above createFhirStoreUtil has a conditional logic for creating FhirStoreUtil (depending on flag values), shouldn't we have the same pattern here? I mean does this work if the fhirStoreUril is a GCP FHIR-store?

hibernate.connection.username=root
hibernate.connection.password=test
hibernate.connection.username=admin
hibernate.connection.password=BHPmyMtaLj6S
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are not standard in OpenMRS, let's revert these (to prevent the impression that there is naything specific with these).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bashir2 should we use a .templates files / another approach to more easily allow individual user settings to be kept as a hibernate.default.properties file without checking it into github?

hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost:3306/atomfeed_client?autoReconnect=true
hibernate.connection.url=jdbc:mysql://openmrs-hapi-db.chijnl5rwr2c.eu-central-1.rds.amazonaws.com:3306/atomfeed_client?autoReconnect=true&useSSL=false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, I feel it is better to keep localhost as the default.

FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(System.getProperty("cloud.gcpFhirStore"), fhirContext);
IParser parser = fhirContext.newJsonParser();
return new FhirConverter(openmrsUtil, fhirStoreUtil, parser);
FhirStoreUtil fhirStoreUtil = new FhirStoreUtil(System.getProperty("cloud.gcpFhirStore"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should be conditional too; also change the property name from cloud.gcpFhirStore to something more generic.

// https://github.com/GoogleCloudPlatform/java-docs-samples/healthcare/tree/master/healthcare/v1
// TODO: remove redundant resource information if passing a HAPI resource
public void uploadResourceToCloud(String resourceType, String resourceId, Resource resource) {
public MethodOutcome uploadResourceToCloud(Resource resource) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's drop ToCloud from this method name as it is now more generic.

@pmanko pmanko force-pushed the 25-hapi-client-to-sink branch 3 times, most recently from 74a77c7 to ccf7807 Compare November 3, 2020 20:16
@pmanko
Copy link
Collaborator Author

pmanko commented Nov 3, 2020

@pmanko Thanks for the great work.
do we need to entirely get rid of the http utils ??

would think it would be useful at some point dealing with non FHIR data .

What do you think

@bashir2 @ibacher any thoughts on keeping HttpUtils in case generic requests are needed in the future?

@pmanko pmanko force-pushed the 25-hapi-client-to-sink branch 2 times, most recently from c0909f9 to 690b080 Compare November 3, 2020 21:27
@pmanko pmanko requested a review from bashir2 November 3, 2020 21:27
@ibacher
Copy link
Collaborator

ibacher commented Nov 4, 2020

@pmanko I'm all for dropping the HttpUtil class until we have a concrete use-case for it. Generally, I don't love keeping code around just "in case".

@mozzy11
Copy link
Collaborator

mozzy11 commented Nov 4, 2020

that makes sense @ibacher

@kimaina
Copy link
Collaborator

kimaina commented Nov 5, 2020

Great work @pmanko! I think we should have an alpha version of the entire pipeline once this PR is merged! Do we have a FHIR store container/docker-compose that I can test it out?

Copy link
Collaborator

@bashir2 bashir2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates @pmanko; comments are mostly minor points.

Also can you please add a TESTED field to your PR description. I am actually thinking of creating a template for PR descriptions including a TESTED field because given that we don't have an e2e test yet and three major way of running pipelines, it is good to know what e2e tests have been done manually or not.

@@ -29,6 +29,8 @@

private static final Logger log = LoggerFactory.getLogger(FhirSearchUtil.class);

FhirStoreUtil fhirStoreUtil;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed anymore, is it?

@@ -14,7 +14,7 @@
package org.openmrs.analytics;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid wildcard imports: style guide.

@@ -51,6 +52,9 @@
@Mock
private OpenmrsUtil openmrsUtil;

@Mock
private FhirStoreUtil fhirStoreUtil;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed either.

@@ -66,6 +70,8 @@

private FhirSearchUtil fhirSearchUtil;

private MethodOutcome outcome;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

private HttpUtil httpUtil;
FhirStoreUtil(String sinkUrl, IRestfulClientFactory clientFactory) throws IllegalArgumentException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this private?

@bashir2
Copy link
Collaborator

bashir2 commented Nov 5, 2020

@pmanko Thanks for the great work.
do we need to entirely get rid of the http utils ??
would think it would be useful at some point dealing with non FHIR data .
What do you think

@bashir2 @ibacher any thoughts on keeping HttpUtils in case generic requests are needed in the future?

I would say let's remove it if it is not used anywhere; we can always resurrect it from the history if it is needed in the future.

updateFhirResource(sinkUrl, resource);
}
catch (Exception e) {
System.out.println(String.format("Exception while sending to sink: %s", e.toString()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use log.error.

@mozzy11
Copy link
Collaborator

mozzy11 commented Nov 10, 2020

@ibacher @kimaina @bashir2 ,this PR looks good to me.
seems its ready for merge and the PLIR work greatly depends on this.
can we merge it in ??

@pmanko pmanko force-pushed the 25-hapi-client-to-sink branch 2 times, most recently from 4cd0bdb to ac99269 Compare November 10, 2020 14:45
@pmanko
Copy link
Collaborator Author

pmanko commented Nov 11, 2020

@ibacher @kimaina @bashir2 ,this PR looks good to me.
seems its ready for merge and the PLIR work greatly depends on this.
can we merge it in ??

@mozzy11 I've been doing some end to end tests to make sure everything is working, so I need to bring some fixes back in. Should be ready to go then.

@mozzy11
Copy link
Collaborator

mozzy11 commented Nov 11, 2020

oh sure @pmanko , thats ok

Copy link
Collaborator

@bashir2 bashir2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @pmanko for the updates. From your other comments it seems that you are not done with the e2e tests yet but I went ahead and took another look to speed up the review process. It would be great if you can finish this soon as I think this is blocking @mozzy11 (please also update the PR description with a TESTED field).

}
}

public MethodOutcome uploadResource(Resource resource) {
Collection<IClientInterceptor> interceptors = Collections.<IClientInterceptor> emptyList();

if (sinkUsername.isEmpty() && !sinkPassword.isEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a ! is missing from the first condition, right?

@@ -74,10 +92,10 @@ public MethodOutcome uploadResource(Resource resource) {
return responses;
}

protected MethodOutcome updateFhirResource(String sinkUrl, Resource resource,
protected MethodOutcome updateFhirResource(String targetUri, Resource resource,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is there a particular reason to call this "target" here but "sink" everywhere else? I don't mind either of these choices but I rather be consistent.

throw new IllegalArgumentException(
String.format("The gcpFhirStore %s does not match Google cloud fhir dataset pattern!", gcpFhirStore));
}
protected GcpStoreUtil(String sinkUrl, String sinkUsername, String sinkPassword, IRestfulClientFactory clientFactory) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think BasicAuth makes sense for the GCP FHIR store and regardless we are not using it here. So I suggest that you drop username/password from the constructor signature here to make it more clear and prevent confusion.

Communication with a FHIR-compliant endpoint - like a HAPI FHIR JPA server
or Google Healthcare APIs - should follow the specifications of the FHIR
standard, and as a result can be facilitated by leveraging a FHIR client
implementation like the [HAPI client](https://hapifhir.io/hapi-fhir/docs/client/introduction.html).
This commit updates the communication with target FHIR repositories to
use this client library.
Copy link
Collaborator

@bashir2 bashir2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @pmanko for the tests; LGTM. Just two minor points/questions.

public MethodOutcome uploadResource(Resource resource) {
Collection<IClientInterceptor> interceptors = Collections.<IClientInterceptor> emptyList();

if (sinkUsername != null && !sinkUsername.isEmpty() && sinkPassword != null && !sinkPassword.isEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These cannot be null can they? Also isNullOrEmpty might be handy.

}
catch (URISyntaxException e) {
log.error(String.format("URI syntax exception while using Google APIs: %s", e.toString()));
catch (Exception e) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we expecting any exceptions here? Or put it another way: Are you trying to catch all runtime exceptions here? If yes, why? I think it is a better practice to only catch checked exceptions.

@bashir2 bashir2 merged commit ebe1eb2 into google:master Nov 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Try using HAPI FHIR client to push data to GCP FHIR store.
5 participants